Android XML资源合并工具

在做聚合SDK开发时,Android方向会遇到从Java编译到APK打包的一系列问题,其中有一项就是需要合并多个工程中AndroidManifest.xml以及strings.xml、styles.xml中的信息,这样才能使用aapt工具生成正确的R.java文件。

但由于AndroidManifest.xml中,字段名中包含 "android:" 带冒号的特殊字符串,所以还无法使用序列化解析XML的方式处理。

目前我通过解码的方式读取出XML中的所有字段信息,保存在结构体的数组对象中,这样既方便记录字段信息,又方便做去重处理。直到全部XML处理完毕,再将结构体生成成一个新的XML文件导出。

1. AndroidManifest.xml合并代码

参数:
-m : 主要XML文件的路径,相同属性以主XML为准。
-l : 合并XML文件的路径。
-o : 导出新XML文件的路径。

package main

import (
    "bytes"
    "encoding/xml"
    "flag"
    "fmt"
    "io/ioutil"
    "os"
    "strings"
)

// TService 服务
type TService struct {
    MainPath              string
    LinkPaths             string
    OutputPath            string
    ManifestAttr          string
    ApplicationAttr       string
    SupportsScreensAttr   string
    UsesSdkAttr           string
    UsesConfigurationAttr string
    InApplication         map[string]*TokenInfo
    OutApplication        map[string]*TokenInfo
}

// TokenInfo 标签信息
type TokenInfo struct {
    Tag  string // 标签
    Name string // 名称
    Attr string // 属性
    Data string // 内容
}

func main() {
    mainPath := flag.String("m", "", "This is the main XML file Path.")
    linkPaths := flag.String("l", "", "This is the link XML files Path. Multiple files must be separated by commas.")
    outputPath := flag.String("o", "", "This is the output XML file Path.")
    flag.Parse()

    fmt.Println("MergeManifestXML Params -m:", *mainPath)
    fmt.Println("MergeManifestXML Params -l:", *linkPaths)
    fmt.Println("MergeManifestXML Params -o:", *outputPath)
    if *mainPath == "" || *linkPaths == "" || *outputPath == "" {
        fmt.Println("Please enter the correct parameters!")
        return
    }

    service := TService{
        MainPath:       *mainPath,
        LinkPaths:      *linkPaths,
        OutputPath:     *outputPath,
        InApplication:  make(map[string]*TokenInfo),
        OutApplication: make(map[string]*TokenInfo),
    }

    service.readMainXMLInfo()
    service.readMergeXMLInfo(service.MainPath)
    files := strings.Split(service.LinkPaths, ",")
    for index := 0; index < len(files); index++ {
        service.readMergeXMLInfo(files[index])
    }
    service.makeManifestXML()
}

func (service *TService) readMainXMLInfo() {
    content, err := ioutil.ReadFile(service.MainPath)
    if err != nil {
        return
    }

    decoder := xml.NewDecoder(bytes.NewBuffer(content))
    for t, err := decoder.Token(); err == nil; t, err = decoder.Token() {
        switch token := t.(type) {
        case xml.StartElement: // 处理元素开始(标签)
            tokenNameLocal := token.Name.Local
            for _, attr := range token.Attr {
                attrSpace := attr.Name.Space
                attrName := attr.Name.Local
                attrValue := attr.Value

                attr := "android:" + attrName + "=" + "\"" + attrValue + "\" "
                if tokenNameLocal == "manifest" {
                    space := attrSpace + ":"
                    if attrSpace == "" || attrSpace == "http://schemas.android.com/apk/res/android" {
                        space = ""
                    }
                    service.ManifestAttr += space + attrName + "=" + "\"" + attrValue + "\" "
                } else if tokenNameLocal == "application" {
                    service.ApplicationAttr += attr
                } else if tokenNameLocal == "supports-screens" {
                    service.SupportsScreensAttr += attr
                } else if tokenNameLocal == "uses-sdk" {
                    service.UsesSdkAttr += attr
                } else if tokenNameLocal == "uses-configuration" {
                    service.UsesConfigurationAttr += attr
                }
            }
        }
    }
}

func (service *TService) readMergeXMLInfo(path string) {
    content, err := ioutil.ReadFile(path)
    if err != nil {
        return
    }

    tokenNum := 0
    isInApplication := false
    isStart, isEnd := false, false
    tempTokenLocalName := ""
    tempTokenInfo := &TokenInfo{}
    decoder := xml.NewDecoder(bytes.NewBuffer(content))
    for t, err := decoder.Token(); err == nil; t, err = decoder.Token() {
        switch token := t.(type) {
        case xml.StartElement: // 处理元素开始(标签)
            tokenNameLocal := token.Name.Local
            if tokenNameLocal == "manifest" || tokenNameLocal == "application" || tokenNameLocal == "supports-screens" || tokenNameLocal == "uses-sdk" || tokenNameLocal == "uses-configuration" {
                if tokenNameLocal == "application" {
                    isInApplication = true
                }
            } else {
                if tokenNum == 0 {
                    tempTokenInfo.Tag = tokenNameLocal
                    for _, attr := range token.Attr {
                        tempTokenInfo.Attr += "android:" + attr.Name.Local + "=" + "\"" + attr.Value + "\" "
                        if attr.Name.Local == "name" {
                            tempTokenInfo.Name = attr.Value
                        }
                    }
                } else {
                    tempTokenLocalName = tokenNameLocal
                    tempTokenInfo.Data += "<" + tokenNameLocal + " "
                    for _, attr := range token.Attr {
                        tempTokenInfo.Data += "android:" + attr.Name.Local + "=" + "\"" + attr.Value + "\" "
                    }
                }
                isStart = true
                tokenNum++
            }
        case xml.EndElement: // 处理元素结束(标签)
            tokenNameLocal := token.Name.Local
            tempTokenLocalName = tokenNameLocal
            if tokenNameLocal == "manifest" || tokenNameLocal == "application" || tokenNameLocal == "supports-screens" || tokenNameLocal == "uses-sdk" || tokenNameLocal == "uses-configuration" {
                if tokenNameLocal == "application" {
                    isInApplication = false
                }
            } else {
                isEnd = true
                tokenNum--
            }
        case xml.CharData: // 处理字符数据(这里就是元素的文本)
            if tempTokenInfo.Name != "" {
                if tokenNum == 0 {
                    tokenInfo := &TokenInfo{}
                    tokenInfo.Tag = tempTokenInfo.Tag
                    tokenInfo.Name = tempTokenInfo.Name
                    tokenInfo.Attr = tempTokenInfo.Attr
                    tokenInfo.Data = tempTokenInfo.Data
                    key := tokenInfo.Tag + "_" + tokenInfo.Name
                    if isInApplication == true {
                        if service.InApplication[key] == nil {
                            service.InApplication[key] = tokenInfo
                        }
                    } else {
                        if service.OutApplication[key] == nil {
                            service.OutApplication[key] = tokenInfo
                        }
                    }
                    tempTokenInfo.Tag = ""
                    tempTokenInfo.Name = ""
                    tempTokenInfo.Attr = ""
                    tempTokenInfo.Data = ""
                } else {
                    if tempTokenInfo.Data != "" {
                        if isStart && isEnd {
                            tempTokenInfo.Data += "/>\n"
                        } else if isStart && !isEnd {
                            tempTokenInfo.Data += ">\n"
                        } else if !isStart && isEnd {
                            tempTokenInfo.Data += "\n"
                        }
                    }
                }
            }
            isStart, isEnd = false, false
        }
    }
}

func (service *TService) makeManifestXML() {
    content := ""
    // 增加 manifest 标签
    content += "\n"

    // 增加 uses-sdk 标签
    if service.UsesSdkAttr != "" {
        content += "\n"
    }

    // 增加 supports-screens 标签
    if service.SupportsScreensAttr != "" {
        content += "\n"
    }

    // 增加 uses-configuration 标签
    if service.UsesConfigurationAttr != "" {
        content += "\n"
    }

    // 增加 application外层 标签
    for _, token := range service.OutApplication {
        content += "<" + token.Tag + " " + token.Attr
        if token.Data == "" {
            content += "/>\n"
        } else {
            content += ">\n"
            content += token.Data
            content += "\n"
        }
    }

    // 增加 application 标签
    content += "\n"

    // 增加 application内层 标签
    for _, token := range service.InApplication {
        content += "<" + token.Tag + " " + token.Attr
        if token.Data == "" {
            content += "/>\n"
        } else {
            content += ">\n"
            content += token.Data
            content += "\n"
        }
    }

    content += "\n"
    content += "\n"
    service.saveNewXML(content)
}

func (service *TService) saveNewXML(content string) {
    fileName := service.OutputPath
    os.Remove(fileName)
    f, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
    if err != nil {
        return
    }
    defer f.Close()
    f.Write([]byte(xml.Header))
    f.Write([]byte(content))
    fmt.Println("Merge ManifestXML Success!")
}

2. strings.xml和styles.xml合并代码

参数:
-m : 主要XML文件的路径,相同属性以主XML为准。
-l : 合并XML文件的路径。
-o : 导出新XML文件的路径。

package main

import (
    "bytes"
    "encoding/xml"
    "flag"
    "fmt"
    "io/ioutil"
    "os"
    "strings"
)

// TXMLData XML属性
type TXMLData struct {
    Data  string
    Names map[string]bool
}

// TService 服务
type TService struct {
    MainPath   string    // 主XML文件路径
    LinkPaths  string    // 依赖XML文件路径
    OutputPath string    // 导出XML文件路径
    XMLData    *TXMLData // XML Style属性
}

func main() {
    mainPath := flag.String("m", "", "This is the main XML files Path.")
    linkPaths := flag.String("l", "", "This is the link XML files Path. Multiple files must be separated by commas.")
    outputPath := flag.String("o", "", "This is the output XML file Path.")
    flag.Parse()

    fmt.Println("MergeResXML Params -m:", *mainPath)
    fmt.Println("MergeResXML Params -l:", *linkPaths)
    fmt.Println("MergeResXML Params -o:", *outputPath)
    if *linkPaths == "" || *outputPath == "" {
        fmt.Println("Please enter the correct parameters!")
        return
    }

    service := &TService{
        MainPath:   *mainPath,
        LinkPaths:  *linkPaths,
        OutputPath: *outputPath,
        XMLData:    &TXMLData{Data: "", Names: make(map[string]bool)},
    }
    service.loadXMLFile()
}

// 读取XML文件
func (service *TService) loadXMLFile() {
    service.saveXMLFileData(service.MainPath)
    files := strings.Split(service.LinkPaths, ",")
    for index := 0; index < len(files); index++ {
        service.saveXMLFileData(files[index])
    }
    service.makeXML()
}

// 保存XML文件数据
func (service *TService) saveXMLFileData(path string) {
    content, err := ioutil.ReadFile(path)
    if err != nil {
        return
    }

    isSaveData := false
    isHasContent := false
    decoder := xml.NewDecoder(bytes.NewBuffer(content))
    for t, err := decoder.Token(); err == nil; t, err = decoder.Token() {
        switch token := t.(type) {
        case xml.StartElement:
            tokenNameLocal := token.Name.Local
            if tokenNameLocal == "style" || tokenNameLocal == "string" {
                for _, attr := range token.Attr {
                    value := attr.Value
                    if attr.Name.Local == "name" && !service.XMLData.Names[value] {
                        service.XMLData.Names[value] = true
                        isSaveData = true
                    }
                }
            }
            if isSaveData {
                service.XMLData.Data += "\n<" + tokenNameLocal
                for _, attr := range token.Attr {
                    service.XMLData.Data += " " + attr.Name.Local + "=\"" + attr.Value + "\""
                }
                service.XMLData.Data += ">"
            }
        case xml.EndElement:
            tokenNameLocal := token.Name.Local
            if isSaveData {
                if tokenNameLocal == "style" || tokenNameLocal == "string" {
                    if isHasContent && tokenNameLocal != "string" {
                        service.XMLData.Data += "\n"
                        isHasContent = false
                    }
                    isSaveData = false
                }
                service.XMLData.Data += ""
            }
        case xml.CharData:
            if isSaveData {
                content := string([]byte(token))
                if strings.TrimSpace(content) != "" {
                    service.XMLData.Data += content
                    isHasContent = true
                }
            }
        }
    }
}

func (service *TService) makeXML() {
    content := ""
    content += service.XMLData.Data
    content += "\n"
    service.saveNewXML(content)
}

func (service *TService) saveNewXML(content string) {
    fileName := service.OutputPath
    os.Remove(fileName)
    f, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
    if err != nil {
        return
    }
    defer f.Close()
    f.Write([]byte(xml.Header))
    f.Write([]byte(content))
    fmt.Println("Merge ManifestXML Success!")
}

你可能感兴趣的:(Android XML资源合并工具)