Core Java 8 读书笔记-XML编程

454 阅读9分钟

封面14 拷贝.png

作者:老九—技术大黍

原文:Core Java 8th Edition

社交:知乎

公众号:老九学堂(新人有惊喜)

特别声明:原创不易,未经授权不得转载或抄袭,如需转载可联系笔者授权

前言

《Essential XML》的作者Don Box曾开玩笑的说:“XML已经替代了Java、设计模式和对象技术成为当前软件工业的主流”。实际上,XML是非常有用的技术用来描述结构化信息。XML不可以解决一切问题,但是它Java配合得非常好,因为从九十年代末,IBM、Apache和其它的大公司都使用高质量的Java库来处理XML。JDK 1.4以后,Sun公司把很多重要的Java库事例到Java API中去了。

XML与HTML之间区别

虽然XML与HTML都有一个根元素,但是它们有不同如下:

  • 不像HTML语言,XML是大小写敏感的,比如H1与h1是不同的XML标签
  • 在HTML中你可以省去结束标签,比如"</p>",但是XML不可能省略结束标签
  • 在XML中如果元素是单个标签,那么必须使用“/”表示结果,比如"<img src="coffeecup.png" />"
  • 在XML中,属性值必须被引号标记。而HTML中,引号是可选的。比如,"<applet code="MyApplet.class" width=300 height=300>是HTML标签,但是不是合法的XML标签。
  • 在HTML中属性可以没有值,但是XML的属性必须带值。比如,"<input type="radio" value="java" checked>

XML文档的结构

一个XML文档一般有一个头或者然后跟一个文档定义标签。

<!DOCTYPE web-app PUBLIC “-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN” “http://java.sun.com/j2ee/dtds/web-app_2_2.dtd”>

最后一个XML文档体还必须包括根元素,比如:

<configuration></configuration>

元素和文本是XML文档的“面包和黄油”。下面是我们可能遇见的标记:

1、字符引用的格式如&#小数值;或者&#十六进制;

   &#2333;
   &#xD9;

2、实体引用的格式如下:

   &lt;
   &gt;
   &amp;
   &quot;
   &apos;

3、CDATA区<![CDTA[ and ]]>--它们是一种特殊格式的字符数据。 are my favorite delimiters]]>这是< & >不再是关键字了。

4、在XML文档中的处理指令。比如

每个XML文档都一个处理指令:

5、注释标签

解析XML文档

处理一个XML文档,我们需要解析它。一个解析器是一个程序,它用来读取XML文档,读取时需要确定该文档的正确格式,然后分析组成元素,最后让程序员访问这些元素。在Java API中提供了两种XML解析器:

  • 树解析器,比如DOM解析器读取XML文档为一个树结构
  • 流解析器,比如SAX解析器,它读取XML时产生事件

DOM解析器是标准的W2C组织的标准,它定义的接口为Document和Element两种。不同的提供商需要实现这些接口,比如Apache和IBM都会实现解析器。下面我们来看一下Sun的XML API的使用方式。

image-20210426143836340.png

Document对象是XML文档在内存中的树结构,它由实现了这些接口的类组成,如上图所示。当我们使用XML API读取XML文档时,我们需要DocumentBuilder对象来实现:

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();

然后才可以读

File file = … 或者URL u = … 或者InputStream in=…
Document doc = builder.parse(file);

然后才可以分析文档的组成内容

Element root = doc.getDocumentElement();

比如我们解析文档

<?xml version=“1.0”?>
<font></font>

呼叫getDocumentElement时会返回font元素,呼叫getTagName方法会把一个元素的标签名称;呼叫getChildNodes方法得到子元素,该返回一个集合或者NodeList接口对象,该对象比标准Java集合先出现。item方法根据一个下标得到相应的项,getLength()方法得到所有item的总数。所以,我们可以这样遍历NodeList对象如下:

NodeList children = root.getChildNodes();
for(for i = 0; i < children.getLength(); i++){
    Node child = children.item(i);
    …
}

image-20210426144418130.png

如果只想要子元素而忽略空白部分,那么代码如下:

for(int i = 0; i < children.getLength(); i++){
    Mode child = children.item(i);
    if(child instanceof Element){
       Element childElement = (Element)child;
       …
    }
}

如果你只想处理两个元素”name”和”size”,那么如果有该文档有DTD,那么解析器可以知道哪些元素没有没有文本,而是一个子元素,同时也可以排除空白。下面的代码是使用getData方法得到存贮在Text结点的字符。

for(int i = 0; i < children.getLength(); i++){
   Node child = children.item(i);
   if(child instanceof Element){
       Element childElement = (Element)child;
       Text textNode = (Text)childElement.getFirstChild();
       String text = textNode.getData().trim();
       if(childElement.getTagName().equals(“name”)){
             name = text;
       }else if(childElement.getTagName().equals(“size”)){
             size = Integer.parseInt(text);
    }
}

解析XML文档时,我们一般API有javax.xml.parsers包和org.w3c.dom包下面的Document和Element以及Node接口。

ShowDOMTree类

package com.jb.arklis.xml;
import static java.lang.System.*;
import java.io.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
import javax.swing.tree.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;

/**
	功能:书写一个类用来测试说明DOM树的概念
	作者:技术大黍
	*/
public class ShowDOMTree{
	
	public static void main(String[] args){
		EventQueue.invokeLater(new Runnable(){
			public void run(){
				JFrame frame = new DOMTreeFrame();
			}
		});
	}
}


/**
	功能:用来演示DOM树的窗体类
	*/
class DOMTreeFrame extends JFrame{
	private DocumentBuilder builder;
	private static final int WIDTH = 650;
	private static final int HEIGHT = 450;
	
	
	public DOMTreeFrame(){
		setTitle("演示DOM树的构成");
		init();
		setSize(WIDTH,HEIGHT);
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		setLocationRelativeTo(null);
		setVisible(true);
	}
	
	private void init(){
		Container container = getContentPane();
		//添加菜单
		JMenu fileMenu = new JMenu("文件");
		JMenuItem openItem = new JMenuItem(" 打开 ");
		//添加打开动作处理事件
		openItem.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent event){
				openFile(); //呼叫执行打开文件的方法
			}
		});
		fileMenu.add(openItem);
		
		//添加退出菜单
		JMenuItem exitItem = new JMenuItem("退出");
		exitItem.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent event){
				exit(0); //正确退出系统
			}
		});
		fileMenu.add(exitItem);
		
		JMenuBar menuBar = new JMenuBar();
		menuBar.add(fileMenu);
		//添加菜单条
		setJMenuBar(menuBar);
	}
	
	protected void openFile(){
		JFileChooser chooser = new JFileChooser();
		chooser.setCurrentDirectory(new File("."));
		//读取文件
		chooser.setFileFilter(new javax.swing.filechooser.FileFilter(){
			//重写accept()方法选择读取的文件
			public boolean accept(File file){
				return file.isDirectory() || file.getName().toLowerCase().endsWith(".xml");
			}
			
			public String getDescription(){
				return "XML文件";
			}
		});
		//从文件选择器返回结果
		int returnInt = chooser.showOpenDialog(this);
		//如果没有找到xml文件
		if(returnInt != JFileChooser.APPROVE_OPTION){
			//那么结果程序
			return;
		}
		//否则处理XML文档
		final File file = chooser.getSelectedFile();
		new SwingWorker<Document, Void>(){
			//实现抽象类的方法
			protected Document doInBackground()throws Exception{
				if(builder == null){
					DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
					builder = factory.newDocumentBuilder();
				}
				//返回解析的DOM
				return builder.parse(file);
			}
			
			protected void done(){
				try{
					Document doc = get();
					//使用JTree来显示DOM对象
					JTree tree = new JTree(new DOMTreeModel(doc));
					tree.setCellRenderer(new DOMTreeCellRender());
					setContentPane(new JScrollPane(tree));
					validate();
				}catch(Exception e){
					e.printStackTrace();
					JOptionPane.showMessageDialog(DOMTreeFrame.this,e);
				}
			}
		}.execute();
	}
}

/**
	功能:书写一个用来处理XML文档为树结构的类
	*/
class DOMTreeModel implements TreeModel{
	private Document doc;
	
	public Object getChild(Object parent, int index){
		Node node = (Node)parent;
		NodeList list = node.getChildNodes();
		return list.item(index);
	}
	
	public DOMTreeModel(Document doc){
		this.doc = doc;
	}
	
	public Object getRoot(){
		return doc.getDocumentElement();
	}
	
	public int getChildCount(Object parent){
		Node node = (Node)parent;
		NodeList list = node.getChildNodes();
		return list.getLength();
	}
	
	public int getIndexOfChild(Object parent, Object child){
		Node node = (Node)parent;
		NodeList list = node.getChildNodes();
		for(int i = 0; i < list.getLength(); i++){
			if(getChild(node,i) == child){
				return i;
			}
		}
		return -1;
	}
	
	public boolean isLeaf(Object node){
		return getChildCount(node) == 0;
	}
	
	public void valueForPathChanged(TreePath path, Object newValue){
		
	}
	
	public void addTreeModelListener(TreeModelListener listener){
		
	}
	
	public void removeTreeModelListener(TreeModelListener listener){
		
	}
}

/**
	功能:书写一个类用来处理XML的结点
	*/
class DOMTreeCellRender extends DefaultTreeCellRenderer{
	public Component getTreeCellRendererComponent(JTree tree, Object value,
		boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus){
		Node node = (Node)value;
		if(node instanceof Element){
			return elementPanel((Element)node);
		}
		super.getTreeCellRendererComponent(tree,value,selected,expanded,leaf,row,hasFocus);
		if(node instanceof CharacterData){
			setText(characterString((CharacterData)node));
		}else{
			setText(node.getClass() + ": " + node.toString());
		}
		return this;
	}
	
	public static JPanel elementPanel(Element element){
		JPanel panel = new JPanel();
		panel.add(new JLabel("元素:" + element.getTagName()));
		final NamedNodeMap map = element.getAttributes();
		panel.add(new JTable(new AbstractTableModel(){
			public int getRowCount(){
				return map.getLength();
			}
			
			public int getColumnCount(){
				return 2;
			}
			
			public Object getValueAt(int row, int count){
				return count == 0 ? map.item(row).getNodeName() : map.item(row).getNodeValue();
			}
		}));
		return panel;
	}
	
	public static String characterString(CharacterData node){
		StringBuilder builder = new StringBuilder(node.getData());
		for(int i = 0; i < builder.length(); i++){
			if(builder.charAt(i) == '\r'){
				builder.replace(i, i + 1, "\\r");
				i++;
			}else if(builder.charAt(i) == '\n'){
				builder.replace(i, i + 1, "\\n");
				i++;
			}else if(builder.charAt(i) == '\t'){
				builder.replace(i, i + 1, "\\t");
				i++;
			}
		}
		if(node instanceof CDATASection){
			builder.insert(0, "CDATASection: ");
		}else if(node instanceof Text){
			builder.insert(0, "文本: ");
		}else if(node instanceof Comment){
			builder.insert(0, "注释: ");
		}
		return builder.toString();
	}
}

运行效果

image-20210426151355028.png

验证XML文档

前面读取XML文档为DOM树,我们看到要处理空白的,以及结点是否是我们所需要的结点内容。为解决这些问题,我们会使用XML解析器的一个功能,就是自动验证XML文档是否为合法的文档。 如果想指明所需要的文档,我们会使用到DTD或者XML Schema来说明客户需求,要求解析器来验证。比如我们使用DTD来指定内容

说明font结点包含的结构,该规则表示了一个font必须有两个子元素,一个叫name另外一个叫size元素。同样我们可以使用Schema来描述:

image-20210426145702095.png

文档类型定义(DTD)

DTD可以写在XML文档,或者写在XML文档外部,它们的使用格式如下:

<?xml version=“1.0”?>
<!DOCTYPE configuration[
   <!ELEMENT configuration …>
   more rules
   …
]>
<configuration></configuration>

或者:

ELEMENT规则可以指定元素有子元素,我们可以使用正则表达式,如下:

image-20210426145901879.png

表示menu元素可以包括多个item元素。

其中PCDATA表示任意一个字符。

image-20210426145945297.png

当我们使用DTD时,我们需要告诉factory打开验证功能: factory.setValidating(true); 这样,解析器才会根据DTD来进行XML文档的验证动作。它最常用的功能是忽略元素内容中的空白。比如:

<font>
    <name>Helvetica</name>
    <size>26</size>
</font>

XML Schema验证

Schema是比DTD更为复杂的验证格式,所以,我们这里只讨论基本的Schema验证。比如指定一个XML文档格式如下:

<?xml version=“1.0”?>
<configuration xmls:sli=http://www.w3.org/2001/XMLSchema-instance xsi:noNapespceSchemaLocation=“config.xsd”></configuration>

以上文档声明config.xsd为验证文档。

一个schema给每个元素定义一个”type”,该类可以是简单类型—使用一个格式约束的字符串—或者复杂的类型。简单类型如下:

  • xsd:string
  • xsd:int:
  • xsd:boolean

我们使用xsd表示一个XML schema的命名空间,有些有使用xs来表示命名空间。

image-20210426150324106.png

当我们需要解析器进行schema验证时,我们需要执行以下步骤:

使用XPath装载信息

如果你想指定XML文档中的一段信息,你可以通过导航DOM树结点来实现,而XPath语言可以访问树结点变得非常简单。 假设有XML文档如下:

<configuration><database>
         <username>dbuser</username>
         <password>secret</password></database>
</configuration>

使用DOM方式的步骤如下:

  • 得到文档结点
  • 枚举出所有的子结点
  • 定位到database元素
  • 得到第一个子元素,即username元素
  • 得到第一个子元素,即一个Text(文本)结点
  • 得到它数据

而使用XPath技术,那么我们只需要使用一句话:

/configuration/database/username

一个XPath可以描述XML文档中一组结点,比如 /gridbag/row 或者指定一个特定的元素 /gridbag/row[1]

XPath使用”[ ]”操作符来选择特定的元素;使用”@”操作符来得到属性值(attribute value) /gridbag/row[1]/cell[1]/@anchor 使用函数来计算元素的重复个数 count(/gridbag/row) 返回row子元素的数量值。 JDK 1.5以后添加执行XPath表达式的API比如

XPathFactory xpFactory = XPathFactory.newInstance();
XPath path = xpFactory.newXPath();
String userName = path.evaluate(“/configuration/database/username”,doc);

ShowXPath类

package com.jb.arklis.xml;
import static java.lang.System.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.xml.namespace.*;
import javax.xml.parsers.*;
import javax.xml.xpath.*;
import org.w3c.dom.*;
import org.xml.sax.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;

/**
	功能:书写一个测试XPath API的类
	作者:技术大黍
	*/
public class ShowXPath{
	
	public static void main(String[] args){
		EventQueue.invokeLater(new Runnable(){
			public void run(){
				new XPathFrame();
			}
		});
	}
}

/**
	功能:显示该演示应用的类
	*/
class XPathFrame extends JFrame{
	private DocumentBuilder builder;
	private Document doc;
	private XPath path;
	private JTextField expression;
	private JTextField result;
	private JTextArea docText;
	private JComboBox typeCombo;
	
	
	public XPathFrame(){
		setTitle("演示XPath API的使用");
		setSize(450,300);
		init();
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		setLocationRelativeTo(null);
		setVisible(true);
	}
	
	private void init(){
		Container container = getContentPane();
		//设置菜单 
		JMenu fileMenu = new JMenu("文档");
		JMenuItem openItem = new JMenuItem("打开");
		//给打开项添加事件处理动作
		openItem.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent event){
				openFile();
			}
		});
		
		fileMenu.add(openItem);
		
		JMenuItem exitItem = new JMenuItem("退出");
		exitItem.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent event){
				exit(0);
			}
		});
		
		fileMenu.add(exitItem);
		
		//把菜单添加到菜单工具条中去
		JMenuBar menuBar = new JMenuBar();
		menuBar.add(fileMenu);
		setJMenuBar(menuBar);//给窗体设置菜单工具条
			
		ActionListener listener = new ActionListener(){
			public void actionPerformed(ActionEvent event){
				evaluate();
			}
		};
		
		expression = new JTextField(20);
		expression.addActionListener(listener);
		JButton evaluateButton = new JButton("执行XPath表达式");
		evaluateButton.addActionListener(listener);
		
		typeCombo = new JComboBox(new Object[]{
			"STRING","NODE","NODESET","NUMBER","BOOLEAN"
		});
		typeCombo.setSelectedItem("STRING");
		
		JPanel panel = new JPanel();
		panel.add(expression);
		panel.add(typeCombo);
		panel.add(evaluateButton);
		docText = new JTextArea(10,40);
		result = new JTextField();
		result.setBorder(new TitledBorder("XPath查询结果:"));
		
		container.add(panel,BorderLayout.NORTH);
		container.add(new JScrollPane(docText),BorderLayout.CENTER);
		container.add(result,BorderLayout.SOUTH);
		
		try{
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			builder = factory.newDocumentBuilder();
		}catch(Exception e){
			e.printStackTrace();
			JOptionPane.showMessageDialog(this,e);
		}
		
		XPathFactory xpFactory = XPathFactory.newInstance();
		path = xpFactory.newXPath();
		pack();
	}
	
	/*处理XML文件的*/
	public void openFile(){
		JFileChooser chooser = new JFileChooser();
		chooser.setCurrentDirectory(new File("."));
		
		chooser.setFileFilter(new javax.swing.filechooser.FileFilter(){
			public boolean accept(File file){
				return file.isDirectory() || file.getName().toLowerCase().endsWith(".xml");
			}
			public String getDescription(){
				return "XML文档";
			}
		});
		
		int row = chooser.showOpenDialog(this);
		if(row != JFileChooser.APPROVE_OPTION){
			return;
		}
		File file = chooser.getSelectedFile();
		//下面是关键代码
		try{
			byte[] bytes = new byte[(int)file.length()];
			new FileInputStream(file).read(bytes);
			docText.setText(new String(bytes));
			doc = builder.parse(file);
		}catch(Exception e){
			JOptionPane.showMessageDialog(this,e);
		}
	}
	
	public void evaluate(){
		try{
			String typeName = (String)typeCombo.getSelectedItem();
			//得到命名空间等
			QName returnType = (QName)XPathConstants.class.getField(typeName).get(null);
			//关键代码
			Object evalResult = path.evaluate(expression.getText(),doc,returnType);
			//如果是结点集合
			if(typeName.equals("NODESET")){
				NodeList list = (NodeList)evalResult;
				StringBuilder message = new StringBuilder();
				message.append("{");
				for(int i = 0; i < list.getLength(); i++){
					if(i > 0){
						message.append(", ");
					}
					message.append("" + list.item(i));
				}
				message.append("}");
				result.setText(message.toString());
			}else{
				result.setText("" + evalResult);
			}
		}catch(Exception e){
			JOptionPane.showMessageDialog(null,e);
		}
	}
}

运行效果

image-20210426151616585.png

使用命名空间和XML流

Java语言使用包来避免类名重复,XML使用相似的东西叫命名空间(namespace)来避免元素和属性重复。一个命名空间使用URI来进行标识,比如 www.w3.org/2001/XMLSch…

HTTP的URL一般用来定位一个文档,所以我们使用URL来对命名空间进行标识。 JDK 1.6把SAX整合到API中去了, 我们可以使用SAX对XML文档进行流解析的动作。我们使用XML API生成XML文档。

ShowXMLWriter类

package com.jb.arklis.xml;
import static java.lang.System.*;
import java.io.*;
import java.net.*;
import javax.xml.parsers.*;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
import java.awt.*;
import java.awt.geom.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;
import javax.xml.stream.*;
import java.util.*;
import org.w3c.dom.*;

/**
	功能:使用SAX API来解析XML文档
	作者:技术大黍
	*/
public class ShowXMLWriter{
	public static void main(String[] args){
		EventQueue.invokeLater(new Runnable(){
			public void run(){
				XMLWriterFrame frame = new XMLWriterFrame();
			}
		});
	}
}

/**
	功能:书写一个显示XML流输出的例子
	作者:Arklis zeng
	时间:2009-06-29
	地点:北大青鸟锦江中心学术部
	版本:ver 1.0.0
	备注:
		
	*/
class XMLWriterFrame extends JFrame{
	private static final int DEFAULT_WIDTH = 450;
	private static final int DEFAULT_HEIGHT = 300;
	private RectangleComponent rectangleComponent;
	private JFileChooser chooser;
	
	public XMLWriterFrame(){
		setTitle("演示XML输出流对象的使用");
		setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
		init();
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		setLocationRelativeTo(null);
		setVisible(true);
	}
	
	private void init(){
		Container container = getContentPane();
		chooser = new JFileChooser();
		rectangleComponent = new RectangleComponent();
		container.add(rectangleComponent);//添加自定义的组件
		JMenuBar menuBar = new JMenuBar();
		setJMenuBar(menuBar);
		//下面是设置菜单项
		JMenu menu = new JMenu("文件");
		menuBar.add(menu);
		
		JMenuItem newItem = new JMenuItem("新建");
		menu.add(newItem);
		newItem.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent event){
				rectangleComponent.newDrawing();
			}
		});
		
		JMenuItem saveItem = new JMenuItem("保存为DOM/XSLT");
		menu.add(saveItem);
		saveItem.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent evnet){
				try{
					saveDocument();
				}catch(Exception e){
					e.printStackTrace();
				}
			}
		});
		
		JMenuItem saveStAxItem = new JMenuItem("保存为标准的XML");
		menu.add(saveStAxItem);
		saveStAxItem.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent event){
				try{
					saveStAX();
				}catch(Exception e){
					e.printStackTrace();
				}
			}
		});
		
		JMenuItem exitItem = new JMenuItem("退出");
		menu.add(exitItem);
		exitItem.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent event){
				exit(0);
			}
		});
	}
	
	public void saveDocument()throws TransformerException, IOException{
		
		if(chooser.showSaveDialog(this) != JFileChooser.APPROVE_OPTION){
			return;
		}
		
		File file = chooser.getSelectedFile();
		Document doc = rectangleComponent.buildDocument();
		Transformer transformer = TransformerFactory.newInstance().newTransformer();
		transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM,
			"http://www.w3.org/TR/2000/CR-SVG-20000802/DTD/svg-20000802.dtd");
		transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC,"-//W3C//DTD SVG 20000802//EN");
		transformer.setOutputProperty(OutputKeys.INDENT,"yes");
		transformer.setOutputProperty(OutputKeys.METHOD,"xml");
		transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
		transformer.transform(new DOMSource(doc),new StreamResult(new FileOutputStream(file)));
	}
	
	public void saveStAX()throws FileNotFoundException,XMLStreamException{
		if(chooser.showSaveDialog(this) != JFileChooser.APPROVE_OPTION){
			return;
		}
		File file = chooser.getSelectedFile();
		XMLOutputFactory factory = XMLOutputFactory.newInstance();
		XMLStreamWriter writer = factory.createXMLStreamWriter(new FileOutputStream(file));
		rectangleComponent.writeDocument(writer);
		writer.close();
	}
}

/**
	功能:处理图形的组件类
	*/
class RectangleComponent extends JComponent{
	private ArrayList<Rectangle2D> rects;
	private ArrayList<Color>  colors;
	private Random generator;
	private DocumentBuilder builder;
	
	public RectangleComponent(){
		rects = new java.util.ArrayList<Rectangle2D>();
		colors = new java.util.ArrayList<Color>();
		generator = new Random();
		
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		try{
			builder = factory.newDocumentBuilder();
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	/**
		产生画图的方法
		*/
	public void newDrawing(){
		int n = 10 + generator.nextInt(20);
		rects.clear();
		colors.clear();
		for(int i = 1; i <= n; i++){
			int x = generator.nextInt(getWidth());
			int y = generator.nextInt(getHeight());
			int width = generator.nextInt(getWidth() - x);
			int height = generator.nextInt(getHeight() - y);
			rects.add(new Rectangle(x,y,width,height));
			int r = generator.nextInt(256);
			int g = generator.nextInt(256);
			int b = generator.nextInt(256);
			colors.add(new Color(r,g,b));
		}
		repaint(); //重绘
	}
	
	//执行重绘的具体动作
	public void paintComponent(Graphics g){
		if(rects.size() == 0){
			newDrawing();
		}
		
		//下面是画rect
		Graphics2D g2 = (Graphics2D)g;
		for(int i = 0; i < rects.size(); i++){
			g2.setPaint(colors.get(i));
			g2.fill(rects.get(i));
		}
	}
	
	/**
		构建文档
		*/
	public Document buildDocument(){
		Document doc = builder.newDocument();
		Element svgElement = doc.createElement("svg");
		doc.appendChild(svgElement);
		svgElement.setAttribute("width","" + getWidth());
		svgElement.setAttribute("height","" + getHeight());
		for(int i = 0; i < rects.size(); i++){
			Color color = colors.get(i);
			Rectangle2D rect = rects.get(i);
			Element rectElement = doc.createElement("rect");
			rectElement.setAttribute("x","" + rect.getX());
			rectElement.setAttribute("y","" + rect.getY());
			rectElement.setAttribute("width","" + rect.getWidth());
			rectElement.setAttribute("height","" + rect.getHeight());
			rectElement.setAttribute("fill","" + colorToString(color));
			svgElement.appendChild(rectElement);
		}
		return doc;
	}
	
	/**
		把当前的图形保存成svg文档
		*/
	public void writeDocument(XMLStreamWriter writer){
		try{
			writer.writeStartDocument();
			writer.writeDTD("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 20000802//EN\" "
				+ "\"http://www.w3.org/TR/2000/CR-SVG-20000802/DTD/svg-20000802.dtd\">");
			writer.writeStartElement("svg");
			writer.writeAttribute("width","" + getWidth());
			writer.writeAttribute("height","" + getHeight());
			
			for(int i = 0; i < rects.size(); i++){
				Color color = colors.get(i);
				Rectangle2D rect = rects.get(i);
				writer.writeEmptyElement("rect");
				writer.writeAttribute("x","" + rect.getX());
				writer.writeAttribute("y","" + rect.getY());
				writer.writeAttribute("width","" + rect.getWidth());
				writer.writeAttribute("height","" + rect.getHeight());
				writer.writeAttribute("fill","" + colorToString(color));
			}
			writer.writeEndDocument();
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	private static String colorToString(Color color){
		StringBuffer buffer = new StringBuffer();
		buffer.append(Integer.toHexString(color.getRGB() & 0xFFFFFF));
		while(buffer.length() < 6){
			buffer.insert(0,'0');
		}
		buffer.insert(0,'#');
		return buffer.toString();
	}
}

运行效果

image-20210426152556900.png

我们把Java2D图形保存为SVG文档。使用XML来处理图形技术参见Apache的Batik小组网站

image-20210426152642754.png

最后

记得给大黍❤️关注+点赞+收藏+评论+转发❤️

作者:老九学堂—技术大黍

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。