`
su1216
  • 浏览: 670964 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
Group-logo
深入入门正则表达式(jav...
浏览量:71916
E60283d7-4822-3dfb-9de4-f2377e30189c
android手机的安全问...
浏览量:128763
社区版块
存档分类
最新评论

使用bash解析xml

    博客分类:
  • bash
阅读更多

最初的需求是希望bash能提供完整成熟的xml解析工具来解析xml,但是并没有找到这样的工具。后来在StackOverFlow上找到一个简单的处理xml的方法,即:

rdom () { local IFS=\> ; read -d \< E C ;}

方法只有一行!(当然,两条语句应该算是两行……)

当然,这也只能处理最简单原始的xml,不能处理带属性的,不能有注释等等。

由于楼主过于懒惰,不想引入(学习)新的脚本语言,所以打算改造上面的方法。

 

改造之前,先来解释一下上面那行语句的意义。

其实很简单,这行命令的作用就是读取<与下一个<之间的字符

xml中,如果在节点本身之外存在<或者>,属性值含有空格则函数失效,所以我们假设xml中没有此情况

有了上面的假设,那么两个<字符直接,就一定会有一个>字符,>将read读取的内容分为两部分,分别记做E和C,举个简单的例子:

<tag>value</tag>

第一次执行rdom时,read读取到<即结束了,所以E和C都是空字符串。

第二次执行rdom时,read读取到的内容为:tag>value,然后是<字符,read结束。所以E=tag;C=value

第三次执行rdom时,read读取到的内容为:/tag>到下一个<或文件末尾。所以E=/tag,C为空白符。

 

所以这种方式并不实用,我们想支持带属性的节点,我们也不想删除xml中的注释,我们甚至还想解析xml的声明,我们……好了,我们想的太多了。我们还是看看能做些什么吧。

我们可以看出,<>里面的部分是作为整体赋值给E的,那么解析属性就要对E做手脚。

(我们假设xml中,在节点本身之外存在没有<和>,属性值中也没有空格 

下面我们来操作一下,首先先引入一个输入空格,用来显示层级的函数echo_tabs

echo_tabs() {
    local tabs="";
    for((i = 0; i < $1; i++)); do
        tabs=$tabs'    ' #4个空格
    done
    echo -n "$tabs" #一定要加双引号
}

然后我们来解析xml中的声明,就是下面这部分 

<?xml version="1.0" encoding="utf-8"?>

声明与其他标签闭合方式不同,并且尖括号内两端是?,所以这里要把它与普通节点区分。 

read_dom() {
    #备份IFS
    local oldIFS=$IFS

    local IFS=\> #字段分割符改为>
    read -d \< ENTITY CONTENT #read分隔符改为<
    local ret=$?
    local ELEMENT=''
    #第一次执行时,第一个字符为<.
    #所以read执行完毕,ENTITY和CONTENT都是空白符
    if [[ $ENTITY =~ ^[[:space:]]*$ ]] && [[ $CONTENT =~ ^[[:space:]]*$ ]]; then
        return $ret
    fi

    # ENTITY = ?xml version="1.0" encoding="utf-8"?
    #解析xml声明,并非普通节点,闭合方式与节点不同
    if [[ "$ENTITY" =~ ^\?xml[[:space:]]*(.*)\?$ ]]; then #使用正则去除问号和xml字符
        ENTITY=''
        ELEMENT='' #不是普通节点
        ATTRIBUTES="${BASH_REMATCH[1]}" #获取声明中的属性
    else #普通节点
        ELEMENT=${ENTITY%% *} #获取节点名称,如果ENTITY中有空格,则第一个空格前面部分即为节点名称
        ATTRIBUTES=${ENTITY#* } #获取节点所有属性,如果ENTITY中有空格,则第一个空格后面部分为所有属性(#2和#4,#4情况下,会多出/)
    fi
}

下面我们来解析注释。注释让人烦恼的地方是,注释内可以包含尖括号!这里只做最简单处理,只解析不含尖括号的注释! 

if [[ "$ENTITY" = \!--*-- ]]; then #不检查注释
    return 0
fi

 

现在我们看xml中最关键的部分

我们知道,CONTENT为节点的内容,显示出来就可以了

if [[ ! "$CONTENT" =~ ^[[:space:]]*$ ]]; then
    echo -n CONTENT=$CONTENT
fi

节点自身属性都在ENTITY中,所以我们需要将节点名称与属性分开,然后再提取属性名和属性值

我们分别处理下面几种形式的节点

<test a="1"/>
<test></test>
<test>abc</test>
<test/>

我们之前已经将节点名称与属性分开了

ELEMENT=${ENTITY%% *} #获取节点名称,如果ENTITY中有空格,则第一个空格前面部分即为节点名称
ATTRIBUTES=${ENTITY#* } #获取节点所有属性,如果ENTITY中有空格,则第一个空格后面部分为所有属性(#2和#4,#4情况下,会多出/)

但是上面的ATTRIBUTES变量会有个小问题,稍后说明

ELEMENT如果以/开头,那么这是读取到节点的闭合标签了

ELEMENT如果以/结尾,那么这是一个空标签,类似<test/>

其他情况ELEMENT均为节点名称,但是读取<test a="1"/>这类标签时,ELEMENT没有问题,ATTRIBUTES是以/结尾,也就是说,这时,标签已经闭合,并且我们需要将/从ATTRIBUTES末尾删除

#!/usr/bin/env bash
#只适合解析简单xml,若属性值带有空格,注释中含有尖括号等,则无法解析
#下面情况可以正常解析
#0.<?xml version="1.0" encoding="utf-8"?>
#1.<test>Only For Test</test>
#2.<application
#      android:label="@string/app_name">
#3.<test/>
#4.<uses-permission android:name="android.permission.BLUETOOTH" />
#Attribute=Attribute Name
#VALUE=Attribute Value
#ELEMENT=Element Name
#CONTENT=Element Content

#接受一个int层级参数,层级从0开始
echo_tabs() {
    local tabs="";
    for((i = 0; i < $1; i++)); do
        tabs=$tabs'    ' #4个空格
    done
    echo -n "$tabs" #一定要加双引号
}

read_dom() {
    #备份IFS
    local oldIFS=$IFS

    local IFS=\> #字段分割符改为>
    read -d \< ENTITY CONTENT #read分隔符改为<
    local ret=$?
    local ELEMENT=''
    #第一次执行时,第一个字符为<.
    #所以read执行完毕,ENTITY和CONTENT都是空白符
    if [[ $ENTITY =~ ^[[:space:]]*$ ]] && [[ $CONTENT =~ ^[[:space:]]*$ ]]; then
        return $ret
    fi

    #第二次执行时,分为下面集中情况
    #0.<?xml version="1.0" encoding="utf-8"?>
    #此时read结果为?xml version="1.0" encoding="utf-8"?
    #CONTENT=若干空白符

    #1.<Size>1785</Size>
    #此时read结果为Size,所以ENTITY=Size,CONTENT='1785'
    #第三次read结为/Size,所以ENTITY=/Size,CONTENT=若干空白符

    #2.<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    #此时read结果为ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/",所以ENTITY=tListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/",CONTENT=同#1

    #3.<test/>
    #此时read结果为test/,所以ENTITY=test/,CONTENT=若干空白符

    #4.<test name="xyz" age="21"/>
    #此时read结果为test name="xyz" age="21"/,所以ENTITY=test name="xyz"/,CONTENT=若干空白符

    #5.<!--q1-->
    #此时read结果为!--q1--,所以ENTITY=!--q1--,CONTENT=''

    # ENTITY = ?xml version="1.0" encoding="utf-8"?
    #解析xml声明,并非普通节点,闭合方式与节点不同
    if [[ "$ENTITY" =~ ^\?xml[[:space:]]*(.*)\?$ ]]; then #使用正则去除问号和xml字符
        ENTITY=''
        ELEMENT='' #不是普通节点
        ATTRIBUTES="${BASH_REMATCH[1]}" #获取声明中的属性
    else #普通节点
        ELEMENT=${ENTITY%% *} #获取节点名称,如果ENTITY中有空格,则第一个空格前面部分即为节点名称
        ATTRIBUTES=${ENTITY#* } #获取节点所有属性,如果ENTITY中有空格,则第一个空格后面部分为所有属性(#2和#4,#4情况下,会多出/)
    fi

    if [[ "$ENTITY" = \!--*-- ]]; then #不检查注释(#5)
        return 0
    fi

    if [[ "$ELEMENT" = /* ]]; then #节点末尾 #1第三步
        tabCount=$[$tabCount - 1]
        echo_tabs $tabCount
        echo END ${ELEMENT#*/} #删除/
        return 0
    elif [[ "$ELEMENT" = */  ]] || [[ $ATTRIBUTES = */  ]]; then #3或#4
        empty=true #节点没有子节点,也没有value(自身为闭合标签)
        if [[ $ATTRIBUTES = */  ]]; then #如果是#4情况
            ATTRIBUTES=${ATTRIBUTES%*/} #将末尾的/删除,提取所有属性
        fi
        echo_tabs $tabCount
        echo -n ELEMENT=${ELEMENT%*/}' '
    elif [ ! "$ELEMENT" = '' ]; then #第一次执行时,ENTITY和CONTENT都是空串
        echo_tabs $tabCount
        echo -n ELEMENT="$ELEMENT"' ' #输出节点名
        tabCount=$[$tabCount + 1] #新节点
    else
        echo -n "XML declaration " #ELEMENT为空,不计算层级
    fi

    local empty=false #没有子节点,没有value
    IFS=$oldIFS #属性之间由空白符分割,恢复IFS,IFS默认为空格/换行/制表符
    local hasAttribute=false #节点是否有属性
    for a in $ATTRIBUTES; do #循环所有属性
        #echo ATTRIBUTES=$ATTRIBUTES '   -+-+-+-   '
        if [[ "$a" = *=* ]] #情况#2和#4
        then
            hasAttribute=true
            ATTRIBUTE_NAME=${a%%=*} #提取属性名
            ATTRIBUTE_VALUE=`tr -d '"' <<< ${a#*=}` #提取属性值并去掉双引号
            echo -n ATTRIBUTE=$ATTRIBUTE_NAME VALUE=$ATTRIBUTE_VALUE' ' #输出属性名/属性值
        fi
    done

    if [[ ! "$CONTENT" =~ ^[[:space:]]*$ ]]; then
        echo -n CONTENT=$CONTENT
    fi

    if [ "$empty" = true ]; then
        echo
        echo_tabs $tabCount
        echo -n END ${ELEMENT%/*} #删除/
#        echo -n ' (empty node)'
    fi

    echo
    return $ret
}

read_xml() {
    local tabCount=0 #用来格式化输出,计算节点层级
    while read_dom; do
        :
    done < "$1"
}  
  
read_xml "$1"

对下面xml执行此脚本

<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

          http://www.apache.org/licenses/LICENSE-2.0

     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.android.test">
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />
    <application android:name=".TestApplication"
                 android:icon="@drawable/icon"
                 android:label="@string/app_name">
        <meta-data android:name="com.google.android.backup.api_key"
            android:value="AEdPqrEAAAAIbiKKs0wlimxeJ9y8iRIaBOH6aeb2IurmZyBHvg" />
        <test>Only For Test</test>
        <test></test>
        <test>abc</test>
        <test/>

        <activity android:name=".cardemulation.AppChooserActivity"
            android:finishOnCloseSystemDialogs="true"
            android:excludeFromRecents="true"/>
        <service android:name=".handover.HandoverService"
            android:process=":handover"
        />
    </application>
</manifest>

输出结果为


 

 

转贴请保留以下链接

本人blog地址

http://su1216.iteye.com/

http://blog.csdn.net/su1216/

  • 大小: 143.9 KB
3
1
分享到:
评论

相关推荐

    Ubuntu下C语言使用libxml2库创建、解析xml文件

    解析XML文件通常涉及打开文件,读取内容,然后遍历XML树。以下是一个基本的解析示例: ```c #include void parseFile(const char* filename) { xmlTextReaderPtr reader = xmlReaderForFile(filename, NULL, NULL...

    xml.zip_XML 解析_XML解析_xml linux

    总的来说,解析XML文件在Unix/Linux环境中涉及多个层面,包括使用命令行工具、编程接口和理解XML的语法规则。无论是为了配置系统、处理网络数据还是进行软件开发,熟悉XML解析都是必备技能之一。

    python解析xml

    首先,我们来了解如何使用ElementTree解析XML。ElementTree库包含两个主要部分:`xml.etree.ElementTree` 和 `xml.etree.cElementTree`。前者是纯Python实现,后者是优化过的C版本,速度更快。基本的使用流程如下: ...

    android java xml 解析生成jar

    6. **解析XML文档**:除了创建XML,JDOM也可以用于解析已存在的XML文件。首先,读取XML文件: ```java SAXBuilder saxBuilder = new SAXBuilder(); Document doc = saxBuilder.build(new File("input.xml")); ``...

    python解析xml生成excel文档

    使用`lxml`库中的`etree`模块来解析XML文件。首先,打开XML文件并将其解析为ElementTree对象。 ```python from lxml import etree def parse_xml(file_path): tree = etree.parse(file_path) root = tree....

    perl的xml::simple解析文件

    Perl中的XML::Simple模块是Perl社区广泛使用的XML解析器,尤其适合处理小型或结构简单的XML文档。这个模块的名称虽然包含“Simple”,但它实际上提供了一种简洁的接口,用于将XML数据转换为Perl数据结构,反之亦然。...

    详解通过libxml库解析XML文件方法

    本文将深入探讨如何使用libxml库来解析XML文档,并通过具体示例进行演示。 #### libxml简介 libxml是一个开源的C语言XML库,支持多种操作系统(包括Linux、Windows等),提供了丰富的API来创建、读取、写入以及...

    读取xml导出为excel

    3. 使用`parse()`方法解析XML文件,事件触发时调用ContentHandler的方法。 二、导出为Excel 将SAX解析得到的数据写入Excel文件,可以使用Python的`pandas`库。首先安装`pandas`和`openpyxl`(用于支持Excel xlsx...

    读写XML文件实例 Javascript

    // 解析XML为JavaScript对象 xml2js.parseString(data, (err, result) =&gt; { if (err) { console.error('Error parsing XML:', err); return; } // 输出解析后的JavaScript对象 console.log('Parsed XML:', ...

    Laravel开发-laravel-xml

    此外,如果你需要处理XML的解析和验证,可以使用`simplexml_load_string`或`DOMDocument::loadXML`来解析XML字符串,`DOMDocument::schemaValidate`来进行XML Schema验证。 总之,Laravel提供了基本的XML响应能力,...

    使用jaxb根据xsd生成xml文档

    在Java开发中,XML文档的生成与解析是一项常见的任务,特别是在处理数据交换和存储时。JAXB(Java Architecture for XML Binding)是Java平台标准的一部分,它提供了一种将Java对象转换为XML文档以及从XML数据恢复...

    CommandScript And XmlParse

    在C++中,我们可以自定义XML解析器,或者利用现有的库如TinyXML、pugixml或Xerces-C++来解析XML文档。这些库提供API接口,允许开发者方便地读取、写入和操作XML元素。XmlParse.cpp可能包含了XML解析的实现逻辑,而...

    xmlr:用于解析非常大的XML文件的Python包

    SAX解析器在读取XML文件时,会逐行或逐事件触发回调函数,这样可以显著减少内存使用,因为它不需要一次性加载整个文件。 在`xmlr`中,用户可以定义自己的回调函数来处理解析过程中遇到的元素、属性等XML结构。这种...

    xml.tar.gz_libxml2_linux c xml_linux xml

    在C++中使用libxml2进行XML解析,你需要包含相应的头文件并链接libxml2库。例如,要使用DOM解析器,你可能需要以下代码片段: ```cpp #include int main() { xmlDocPtr doc = xmlReadFile("example.xml", "UTF-8...

    java解析apk文件

    3. **解析XML**:一旦找到AndroidManifest.xml,使用`DocumentBuilderFactory`和`DocumentBuilder`解析XML内容。这些类属于Java的`javax.xml.parsers`包。 4. **提取信息**:从解析后的XML文档中,可以使用`XPath`...

    xls数据的读写(基于xml)

    # 解析XML文件 tree = etree.parse('path_to_your_xml_file.xml') # 获取根元素 root = tree.getroot() # 遍历行和列 for row in root.findall('.//sheetData/row'): cells = row.findall('c') for cell in ...

    Laravel开发-laravel-xml-middleware

    对于`laravel-xml-middleware`,`handle`方法中将解析XML请求体,并将其转换为PHP数组,以便Laravel能够正常处理。 接下来,我们来探讨如何在Laravel项目中安装和使用这个中间件。首先,通过Composer将`laravel-xml...

    前端开源库-data2xml

    通过深入理解data2xml库,前端开发者可以轻松地处理XML相关的任务,如构建XML请求、解析XML响应等。这个库的灵活性和易用性使得它在前端开发中成为了一项实用工具,尤其是当项目需要与XML相关的功能时。 在压缩包`...

    node.j 通过HTTP传递XML

    为了实际解析XML,你可以使用第三方库如`xml2js`,它能将XML字符串转换为JavaScript对象,便于处理。记得先通过npm安装`xml2js`: ```bash npm install xml2js ``` 然后,在服务器中引入并使用它: ```javascript...

    前端开源库-xml-loader

    `xml-loader`还支持自定义配置,允许用户根据实际需求调整XML解析的行为。例如,可以通过设置`objectOptions`来自定义如何将XML转换为JavaScript对象,或者使用`transform`选项来应用自定义的转换函数。 在实际项目...

Global site tag (gtag.js) - Google Analytics