溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

使用bash解析xml的案例分析

發(fā)布時(shí)間:2020-09-22 11:51:58 來源:億速云 閱讀:222 作者:小新 欄目:編程語言

這篇文章給大家分享的是有關(guān)使用bash解析xml的案例分析的內(nèi)容。小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考。一起跟隨小編過來看看吧。

 最初的需求是希望bash能提供完整成熟的xml解析工具來解析xml,但是并沒有找到這樣的工具。后來在StackOverFlow上找到一個(gè)簡(jiǎn)單的處理xml的方法,即:

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

方法只有一行!(當(dāng)然,兩條語句應(yīng)該算是兩行……)

當(dāng)然,這也只能處理最簡(jiǎn)單原始的xml,不能處理帶屬性的,不能有注釋等等。

由于樓主過于懶惰,不想引入(學(xué)習(xí))新的腳本語言,所以打算改造上面的方法。

改造之前,先來解釋一下上面那行語句的意義。

其實(shí)很簡(jiǎn)單,這行命令的作用就是讀取<與下一個(gè)<之間的字符

(xml中,如果在節(jié)點(diǎn)本身之外存在<或者>,屬性值含有空格,則函數(shù)失效,所以我們假設(shè)xml中沒有此情況)

有了上面的假設(shè),那么兩個(gè)<字符直接,就一定會(huì)有一個(gè)>字符,>將read讀取的內(nèi)容分為兩部分,分別記做E和C,舉個(gè)簡(jiǎn)單的例子:

<tag>value</tag>

第一次執(zhí)行rdom時(shí),read讀取到<即結(jié)束了,所以E和C都是空字符串。

第二次執(zhí)行rdom時(shí),read讀取到的內(nèi)容為:tag>value,然后是<字符,read結(jié)束。所以E=tag;C=value

第三次執(zhí)行rdom時(shí),read讀取到的內(nèi)容為:/tag>到下一個(gè)<或文件末尾。所以E=/tag,C為空白符。

所以這種方式并不實(shí)用,我們想支持帶屬性的節(jié)點(diǎn),我們也不想刪除xml中的注釋,我們甚至還想解析xml的聲明,我們……好了,我們想的太多了。我們還是看看能做些什么吧。

我們可以看出,<>里面的部分是作為整體賦值給E的,那么解析屬性就要對(duì)E做手腳。

(我們假設(shè)xml中,在節(jié)點(diǎn)本身之外存在沒有<和>,屬性值中也沒有空格)

下面我們來操作一下,首先先引入一個(gè)輸入空格,用來顯示層級(jí)的函數(shù)echo_tabs

echo_tabs() {
    local tabs="";
    for((i = 0; i < $1; i++)); do
        tabs=$tabs'    ' #4個(gè)空格
    done
    echo -n "$tabs" #一定要加雙引號(hào)
}

然后我們來解析xml中的聲明,就是下面這部分

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

聲明與其他標(biāo)簽閉合方式不同,并且尖括號(hào)內(nèi)兩端是?,所以這里要把它與普通節(jié)點(diǎn)區(qū)分。

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

    local IFS=\> #字段分割符改為>
    read -d \< ENTITY CONTENT #read分隔符改為<
    local ret=$?
    local ELEMENT=''
    #第一次執(zhí)行時(shí),第一個(gè)字符為<.
    #所以read執(zhí)行完畢,ENTITY和CONTENT都是空白符
    if [[ $ENTITY =~ ^[[:space:]]*$ ]] && [[ $CONTENT =~ ^[[:space:]]*$ ]]; then
        return $ret
    fi

    # ENTITY = ?xml version="1.0" encoding="utf-8"?
    #解析xml聲明,并非普通節(jié)點(diǎn),閉合方式與節(jié)點(diǎn)不同
    if [[ "$ENTITY" =~ ^\?xml[[:space:]]*(.*)\?$ ]]; then #使用正則去除問號(hào)和xml字符
        ENTITY=''
        ELEMENT='' #不是普通節(jié)點(diǎn)
        ATTRIBUTES="${BASH_REMATCH[1]}" #獲取聲明中的屬性
    else #普通節(jié)點(diǎn)
        ELEMENT=${ENTITY%% *} #獲取節(jié)點(diǎn)名稱,如果ENTITY中有空格,則第一個(gè)空格前面部分即為節(jié)點(diǎn)名稱
        ATTRIBUTES=${ENTITY#* } #獲取節(jié)點(diǎn)所有屬性,如果ENTITY中有空格,則第一個(gè)空格后面部分為所有屬性(#2和#4,#4情況下,會(huì)多出/)
    fi
}

下面我們來解析注釋。注釋讓人煩惱的地方是,注釋內(nèi)可以包含尖括號(hào)!這里只做最簡(jiǎn)單處理,只解析不含尖括號(hào)的注釋!

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

現(xiàn)在我們看xml中最關(guān)鍵的部分

我們知道,CONTENT為節(jié)點(diǎn)的內(nèi)容,顯示出來就可以了

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

節(jié)點(diǎn)自身屬性都在ENTITY中,所以我們需要將節(jié)點(diǎn)名稱與屬性分開,然后再提取屬性名和屬性值

我們分別處理下面幾種形式的節(jié)點(diǎn)

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

我們之前已經(jīng)將節(jié)點(diǎn)名稱與屬性分開了

ELEMENT=${ENTITY%% *} #獲取節(jié)點(diǎn)名稱,如果ENTITY中有空格,則第一個(gè)空格前面部分即為節(jié)點(diǎn)名稱
ATTRIBUTES=${ENTITY#* } #獲取節(jié)點(diǎn)所有屬性,如果ENTITY中有空格,則第一個(gè)空格后面部分為所有屬性(#2和#4,#4情況下,會(huì)多出/)

但是上面的ATTRIBUTES變量會(huì)有個(gè)小問題,稍后說明

ELEMENT如果以/開頭,那么這是讀取到節(jié)點(diǎn)的閉合標(biāo)簽了

ELEMENT如果以/結(jié)尾,那么這是一個(gè)空標(biāo)簽,類似<test/>

其他情況ELEMENT均為節(jié)點(diǎn)名稱,但是讀取<test a="1"/>這類標(biāo)簽時(shí),ELEMENT沒有問題,ATTRIBUTES是以/結(jié)尾,也就是說,這時(shí),標(biāo)簽已經(jīng)閉合,并且我們需要將/從ATTRIBUTES末尾刪除

#!/usr/bin/env bash
#只適合解析簡(jiǎn)單xml,若屬性值帶有空格,注釋中含有尖括號(hào)等,則無法解析
#下面情況可以正常解析
#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

#接受一個(gè)int層級(jí)參數(shù),層級(jí)從0開始
echo_tabs() {
    local tabs="";
    for((i = 0; i < $1; i++)); do
        tabs=$tabs'    ' #4個(gè)空格
    done
    echo -n "$tabs" #一定要加雙引號(hào)
}

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

    local IFS=\> #字段分割符改為>
    read -d \< ENTITY CONTENT #read分隔符改為<
    local ret=$?
    local ELEMENT=''
    #第一次執(zhí)行時(shí),第一個(gè)字符為<.
    #所以read執(zhí)行完畢,ENTITY和CONTENT都是空白符
    if [[ $ENTITY =~ ^[[:space:]]*$ ]] && [[ $CONTENT =~ ^[[:space:]]*$ ]]; then
        return $ret
    fi

    #第二次執(zhí)行時(shí),分為下面集中情況
    #0.<?xml version="1.0" encoding="utf-8"?>
    #此時(shí)read結(jié)果為?xml version="1.0" encoding="utf-8"?
    #CONTENT=若干空白符

    #1.<Size>1785</Size>
    #此時(shí)read結(jié)果為Size,所以ENTITY=Size,CONTENT='1785'
    #第三次read結(jié)為/Size,所以ENTITY=/Size,CONTENT=若干空白符

    #2.<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    #此時(shí)read結(jié)果為L(zhǎng)istBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/",
    所以ENTITY=tListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/",CONTENT=同#1

    #3.<test/>
    #此時(shí)read結(jié)果為test/,所以ENTITY=test/,CONTENT=若干空白符

    #4.<test name="xyz" age="21"/>
    #此時(shí)read結(jié)果為test name="xyz" age="21"/,所以ENTITY=test name="xyz"/,CONTENT=若干空白符

    #5.<!--q1-->
    #此時(shí)read結(jié)果為!--q1--,所以ENTITY=!--q1--,CONTENT=''

    # ENTITY = ?xml version="1.0" encoding="utf-8"?
    #解析xml聲明,并非普通節(jié)點(diǎn),閉合方式與節(jié)點(diǎn)不同
    if [[ "$ENTITY" =~ ^\?xml[[:space:]]*(.*)\?$ ]]; then #使用正則去除問號(hào)和xml字符
        ENTITY=''
        ELEMENT='' #不是普通節(jié)點(diǎn)
        ATTRIBUTES="${BASH_REMATCH[1]}" #獲取聲明中的屬性
    else #普通節(jié)點(diǎn)
        ELEMENT=${ENTITY%% *} #獲取節(jié)點(diǎn)名稱,如果ENTITY中有空格,則第一個(gè)空格前面部分即為節(jié)點(diǎn)名稱
        ATTRIBUTES=${ENTITY#* } #獲取節(jié)點(diǎn)所有屬性,如果ENTITY中有空格,則第一個(gè)空格后面部分為所有屬性(#2和#4,#4情況下,會(huì)多出/)
    fi

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

    if [[ "$ELEMENT" = /* ]]; then #節(jié)點(diǎn)末尾 #1第三步
        tabCount=$[$tabCount - 1]
        echo_tabs $tabCount
        echo END ${ELEMENT#*/} #刪除/
        return 0
    elif [[ "$ELEMENT" = */  ]] || [[ $ATTRIBUTES = */  ]]; then #3或#4
        empty=true #節(jié)點(diǎn)沒有子節(jié)點(diǎn),也沒有value(自身為閉合標(biāo)簽)
        if [[ $ATTRIBUTES = */  ]]; then #如果是#4情況
            ATTRIBUTES=${ATTRIBUTES%*/} #將末尾的/刪除,提取所有屬性
        fi
        echo_tabs $tabCount
        echo -n ELEMENT=${ELEMENT%*/}' '
    elif [ ! "$ELEMENT" = '' ]; then #第一次執(zhí)行時(shí),ENTITY和CONTENT都是空串
        echo_tabs $tabCount
        echo -n ELEMENT="$ELEMENT"' ' #輸出節(jié)點(diǎn)名
        tabCount=$[$tabCount + 1] #新節(jié)點(diǎn)
    else
        echo -n "XML declaration " #ELEMENT為空,不計(jì)算層級(jí)
    fi

    local empty=false #沒有子節(jié)點(diǎn),沒有value
    IFS=$oldIFS #屬性之間由空白符分割,恢復(fù)IFS,IFS默認(rèn)為空格/換行/制表符
    local hasAttribute=false #節(jié)點(diǎn)是否有屬性
    for a in $ATTRIBUTES; do #循環(huán)所有屬性
        #echo ATTRIBUTES=$ATTRIBUTES '   -+-+-+-   '
        if [[ "$a" = *=* ]] #情況#2和#4
        then
            hasAttribute=true
            ATTRIBUTE_NAME=${a%%=*} #提取屬性名
            ATTRIBUTE_VALUE=`tr -d '"' <<< ${a#*=}` #提取屬性值并去掉雙引號(hào)
            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 #用來格式化輸出,計(jì)算節(jié)點(diǎn)層級(jí)
    while read_dom; do
        :
    done < test.xml
}

read_xml

對(duì)下面xml執(zhí)行此腳本

<?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>

輸出結(jié)果為

使用bash解析xml的案例分析

感謝各位的閱讀!關(guān)于使用bash解析xml的案例分析就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,讓大家可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到吧!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI