Javafx XML Treeview 中的解析器区分 <xxx>abc</xxx> 和 <abc/>

Javafx XML Parser in Treeview distinguish between <xxx>abc</xxx> and <abc/>

我尝试将以下 XML 文件解析为树视图。

效果不错。

但是在使用鼠标选择节点时,我需要区分 "Endnodes" 和空节点。

我尝试使用 selectedItem.getChildren().size() 和 selectedItem.isLeaf().

但两者都为 "endnode" 和空 "test" 节点提供相同的结果。

不幸的是我无法更改 xml 结构,因为 ist 是自动生成的。

有人可以帮我吗?

<root>
  <type_1>
    <element_1>
        EndNode
    </element_1>
  </type_1>
  <type_2>
    <element_2>
       <test>Endnode</test>
       <test/>
    </element_2>
  </type_2>
</root>

我从在 SO 找到的示例开始,然后根据需要对其进行修改。

JavaFX

 public static TreeItem<String> readData(File file) throws SAXException, ParserConfigurationException, IOException {
    SAXParserFactory parserFactory = SAXParserFactory.newInstance();
    SAXParser parser = parserFactory.newSAXParser();
    XMLReader reader = parser.getXMLReader();
    TreeItemCreationContentHandler contentHandler = new TreeItemCreationContentHandler();

    // parse file using the content handler to create a TreeItem representation
    reader.setContentHandler(contentHandler);
    reader.parse(file.toURI().toString());

    // use first child as root (the TreeItem initially created does not contain data from the file)
    TreeItem<String> item = contentHandler.item.getChildren().get(0);

    contentHandler.item.getChildren().clear();
    return item;
}

@Override
public void initialize(URL url, ResourceBundle rb) {

    TreeItem<String> root;
    try {
        root = readData(new File("test.xml"));
        treeType.setRoot(root);
    } catch (Exception ex) {
    }
    treeType.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<TreeItem<String>>() {

        @Override
        public void changed(
                ObservableValue<? extends TreeItem<String>> observable, TreeItem<String> old_val,
                TreeItem<String> new_val
        ) {
            TreeItem<String> selectedItem = new_val;
            System.out.println(selectedItem.getChildren().size() + " : " + selectedItem.isLeaf());

        }

    }
    );

-

class TreeItemCreationContentHandler extends DefaultHandler {

  public TreeItem<String> item = new TreeItem<>();

  @Override
  public void endElement(String uri, String localName, String qName) throws SAXException {
      this.item = this.item.getParent();
  }

  @Override
  public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
      TreeItem<String> item = new TreeItem<>(qName);
      this.item.getChildren().add(item);
      this.item = item;
  }

  @Override
  public void characters(char[] ch, int start, int length) throws SAXException {

      String s = String.valueOf(ch, start, length).trim();
      if (!s.isEmpty()) {
          // add text content as new child
          this.item.getChildren().add(new TreeItem<>(s));
      }
  }

  }

插件: 我也很乐意提示如何自动突出显示端节点。

通过使用 TreeView<String>,您无法区分元素和字符数据。相反,定义一个封装它的 class(或 classes)。例如

public class XMLNode {

    private final String text ;
    private final boolean characterData ;

    public XMLNode(String text, boolean isCharacterData) {
        this.text = text ;
        this.characterData = isCharacterData ;
    }

    public boolean isElement() {
        return ! characterData ;
    }

    public boolean isCharacterData() {
        return characterData ;
    }

    @Override
    public boolean equals(Object obj) {
        if (! (obj instanceof XMLNode)) {
            return false ;
        }
        XMLNode other = (XMLNode) obj ;
        return other.characterData == characterData 
            && Objects.equals(other.text, text);
    }

    @Override
    public int hashCode() {
        return Objects.hash(text, characterData);
    }

    @Override
    public String toString() {
        return text ;
    }

}

现在您可以将 TreeView<String>TreItem<String> 更改为 TreeView<XMLNode>TreeItem<XMLNode>,然后执行

class TreeItemCreationContentHandler extends DefaultHandler {

  public TreeItem<XMLNode> item = new TreeItem<>();

  @Override
  public void endElement(String uri, String localName, String qName) throws SAXException {
      this.item = this.item.getParent();
  }

  @Override
  public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
      TreeItem<XMLNode> item = new TreeItem<>(new XMLNode(qName, false));
      this.item.getChildren().add(item);
      this.item = item;
  }

  @Override
  public void characters(char[] ch, int start, int length) throws SAXException {

      String s = String.valueOf(ch, start, length).trim();
      if (!s.isEmpty()) {
          // add text content as new child
          this.item.getChildren().add(new TreeItem<>(new XMLNode(s, true)));
      }
  }

}

现在您可以在选择处理程序中区分:

treeType.getSelectionModel().selectedItemProperty().addListener((obs, oldSelection, newSelection) -> {
    XMLNode node = newSelection.getValue();
    if (node.isCharacterData()) {
        // ...
    }
    if (node.isElement()) {
        // ...
    }
});

如果你想突出显示字符节点(或以其他方式显示它们),你只需要一个自定义单元格:

treeType.setCellFactory(tv -> new TreeCell<XMLNode>() {
    @Override
    protected void updateItem(XMLNode item, boolean empty) {
        super.updateItem(item, empty) ;
        setStyle("");
        if (empty) {
            setText("");
        } else {
            setText(item.toString());
            if (item.isCharacterData()) {
                setStyle("-fx-background-color: yellow;");
            }
        }
    }
});

这是一个 SSCCE:

import java.io.IOException;
import java.io.StringReader;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.stage.Stage;

public class XMLTree extends Application {

    private final String xml = "<root>\n" + 
            "  <type_1>\n" + 
            "    <element_1>\n" + 
            "        EndNode\n" + 
            "    </element_1>\n" + 
            "  </type_1>\n" + 
            "  <type_2>\n" + 
            "    <element_2>\n" + 
            "       <test>Endnode</test>\n" + 
            "       <test/>\n" + 
            "    </element_2>\n" + 
            "  </type_2>\n" + 
            "</root>";

    @Override
    public void start(Stage primaryStage) throws Exception {
        TreeView<XMLNode> treeType = new TreeView<>();
        treeType.setRoot(readData(xml));
        treeType.setCellFactory(tv -> new TreeCell<XMLNode>() {
            @Override
            protected void updateItem(XMLNode item, boolean empty) {
                super.updateItem(item, empty) ;
                setStyle("");
                if (empty) {
                    setText("");
                } else {
                    setText(item.toString());
                    if (item.isCharacterData()) {
                        setStyle("-fx-background-color: yellow;");
                    }
                }
            }
        });

        treeType.getSelectionModel().selectedItemProperty().addListener((obs, oldSelection, newSelection) -> {
            XMLNode node = newSelection.getValue();
            if (node.isCharacterData()) {
                System.out.println("Selected characters: "+node);
            }
            if (node.isElement()) {
                System.out.println("Selected element: <"+node+">");
            }
        });

        Scene scene = new Scene(treeType);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static TreeItem<XMLNode> readData(String data) throws SAXException, ParserConfigurationException, IOException {
        SAXParserFactory parserFactory = SAXParserFactory.newInstance();
        SAXParser parser = parserFactory.newSAXParser();
        XMLReader reader = parser.getXMLReader();
        TreeItemCreationContentHandler contentHandler = new TreeItemCreationContentHandler();

        // parse file using the content handler to create a TreeItem representation
        reader.setContentHandler(contentHandler);
        reader.parse(new InputSource(new StringReader(data)));

        // use first child as root (the TreeItem initially created does not contain data from the file)
        TreeItem<XMLNode> item = contentHandler.item.getChildren().get(0);

        contentHandler.item.getChildren().clear();
        return item;
    }

    public static class TreeItemCreationContentHandler extends DefaultHandler {

        public TreeItem<XMLNode> item = new TreeItem<>();

        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException {
            this.item = this.item.getParent();
        }

        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            TreeItem<XMLNode> item = new TreeItem<>(new XMLNode(qName, false));
            this.item.getChildren().add(item);
            this.item = item;
        }

        @Override
        public void characters(char[] ch, int start, int length) throws SAXException {

            String s = String.valueOf(ch, start, length).trim();
            if (!s.isEmpty()) {
                // add text content as new child
                this.item.getChildren().add(new TreeItem<>(new XMLNode(s, true)));
            }
        }

        }

    public static void main(String[] args) {
        launch(args);
    }
}

注意:我对 DOM classes 不是很熟悉(虽然当我使用它们时我发现它不是一个很容易 API工作);因此您可以使用现有 API 而不是自定义 XMLNode class。不过,这应该会给您一些想法。