XML(五):逐步分析DOM解析XML之增、保存、缩进

XML文件源码如下:

<?xml version="1.0" encoding="UTF-8"?>
<PhoneInfo>
    <Brand name="华为">
        <Type name="U8650"/>
    </Brand>
    <Brand name="苹果">

        <Type name="iPhone4"/>
        <Type name="iPhone5"/>
    </Brand>
</PhoneInfo>

一、增

要在PhoneInfo的节点上添加品牌节点,需要先找到PhoneInfo节点。然后在此DOM树上创建一个新的Brand品牌节点,设置它的属性name为“MOTO”。然后根据它在DOM树的位置,把它添加为PhoneInfo的子节点。这样,此DOM树就有了新的结构。最后把这个DOM树结构保存到XML文件就可以了。
综上,实现步骤如下:
1、为XML文档构造DOM树
2、创建新节点,并设置name属性
3、把节点加到其所属父节点上
4、保存XML文档
其实整体的思路就是我先在DOM树外创建元素节点,然后再把元素节点添加到DOM树中,用到的方法主要是:

createElement(元素类型)  创建一个某种类型的元素
setAttribute(属性,   属性值) 为该元素添加属性
父元素.appendChild(子元素)添加父子关系

代码如下:

// 添加节点信息
    public void addBrand() {
        // 创建Brand节点
        Element brandElement = document.createElement("Brand");
        brandElement.setAttribute("name", "MOTO");
        // 创建Type节点
        Element typeElement = document.createElement("Type");
        typeElement.setAttribute("name", "A1680");
        // 添加父子关系
        brandElement.appendChild(typeElement);
        // 在DOM树外把标签为PhoneInfo的元素拿出来,以方便下一步把新建的节点加进去
        Element phoneElement = (Element)document.getElementsByTagName("PhoneInfo").item(0);
        // 添加父子关系
        phoneElement.appendChild(brandElement);
        // 把新的DOM树保存到XML文件之中
        saveXML();
}

下面这行代码:

 Element phoneElement = (Element)document.getElementsByTagName("PhoneInfo").item(0);

的意思是:通过Document拿到标签名字为PhoneInfo的标签,把该节点强转为Element类型后,然后再把新建的Brand节点加进去。

注:上面这些操作都是在操作内存中的DOM树,操作完毕之后内存中的DOM树变化了,但是XML文件并没有改变。还需要将内存中的DOM树保存到XML文件中。

二、保存

保存XML文件,需要借助转换器:源(最新的DOM树)-->目的地(手机信息.xml),借助输出流来实现。
代码如下:

// 保存XML文件
    public void saveXML() {
        // 转换器工厂
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        try {
            // 转换器
            Transformer transformer = transformerFactory.newTransformer();
        
            DOMSource domSource = new DOMSource(document);
            StreamResult result = new StreamResult(new FileOutputStream("手机信息.xml"));
            transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
            // 转换器的方法,对应于解析器的解析方法(parse())
            transformer.transform(domSource, result);
        } catch (TransformerConfigurationException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (TransformerException e) {
            e.printStackTrace();
        }       
   }

分析:
1、保存XML文件需要借助转换器,这个跟解析XML文件时用的解析器相对应。如下:
转换器工厂对应解析器工厂TransformerFactory transformerFactory = TransformerFactory.newInstance();对应DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();;转换器对应解析器Transformer transformer = transformerFactory.newTransformer();对应DocumentBuilder db = factory.newDocumentBuilder();;转换方法对应解析方法:parse()对应transform()。两个方法需要的参数不一样,返回的结果也不一样。
parse()方法需要的参数是一个XML文件,返回结果是一个DOM树。transform()方法需要的参数是(要保存的DOM源和一个Result)(transformer.transform(Source, Result);)。
2、对于transform()方法中的两个参数,Source和Result,在XML文档的保存中分别是DOMSource和StreamResult。DOMSource很简单,就是直接把document作为参数传进去即可:DOMSource domSource = new DOMSource(document);
但是StreamResult构造对象时有三种常用构造方法,参数分别不同:
第一种:StreamResult result = new StreamResult("手机信息.xml");直接把文件传进来作为参数,但是此时需要制定文档编码格式,否则可能在最后的XML文件中出现乱码。指定字符编码格式代码如下:transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
第二种:StreamResult result = new StreamResult(new FileOutputStream("手机信息.xml"));将一个OutputStream类型的数据作为参数传进去。但是此时也需要制定编码格式:transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
第三种:将一个Writer类型的数据作为参数传进去:

    OutputStream out = new FileOutputStream("手机信息.xml");
    StreamResult result = new StreamResult(new OutputStreamWriter(out, "UTF-8"));

此时不需要前两种方法的指定编码格式了,因为OutputStreamWriter()对象在输出是可以直接指定编码格式。
保存之后的XML文件如下:

<?xml version="1.0" encoding="UTF-8" standalone="no"?><PhoneInfo>
    <Brand name="华为">
        <Type name="123"/>
        <Type name="456"/>
        <Type name="789"/>
    </Brand>
     <Brand name="iphone">
        <Type name="iphone6"/>
        <Type name="iphone7"/>
        <Type name="iphone4"/>
    </Brand>
<Brand name="香蕉"><Type name="A1680"/></Brand></PhoneInfo>

注1:OutputKeys.ENCODING意思是设置输出时候的编码。OutputKeys是输出时候的关键字,里面定义了一些静态方法,可以设置输出时候的一些格式等。

注2:如果出现中文乱码,需要

 StreamResult result = new StreamResult(new OutputStreamWriter(out, "UTF-8"));
 transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");

两句同时写上才有用。

三、设置缩进

从上面保存后的XML文件来看,只是增加在最后一行,没有设置缩进,看着非常别扭。那怎么设置缩进呢?
设置缩进分两步:
第一步:允许缩进

transformerFactory.setAttribute("indent-number", 4);

通过调用转换器工厂的这个方法可以设置缩进的空格数目。
第二步:允许缩进

transformer.setOutputProperty(OutputKeys.INDENT, "YES");

在转换器中把OutputKeys的关键字INDENT相应的值设置为YES,这就表示允许在将内存中的DOM树写进XML文件的时候设置缩进。
加上设置缩进的代码之后,代码如下:

public void saveXML() {
        // 转换器工厂
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        // 设置缩进
        transformerFactory.setAttribute("indent-number", 4);
        try {
            // 转换器
            Transformer transformer = transformerFactory.newTransformer();
            DOMSource domSource = new DOMSource(document);
            OutputStream out = new FileOutputStream("手机信息.xml");
            transformer.setOutputProperty(OutputKeys.INDENT, "YES");
            StreamResult result = new StreamResult(new OutputStreamWriter(out, "UTF-8")); 
            transformer.transform(domSource, result);
        } catch (TransformerConfigurationException e) {
            e.printStackTrace();
        } catch (TransformerException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }       
}

最终的XML文件如下:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<PhoneInfo>
    <Brand name="华为">
        <Type name="123"/>
        <Type name="456"/>
        <Type name="789"/>
    </Brand>
    <Brand name="iphone">
        <Type name="iphone6"/>
        <Type name="iphone7"/>
        <Type name="iphone4"/>
    </Brand>
<Brand name="香蕉">
        <Type name="A1680"/>
    </Brand>
</PhoneInfo>

注:倒数第四行是没有缩进的,但这不是代码的问题,而是MyEclipse底层源码的问题,忽略不计即可。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容