美文网首页
Android XML资源合并工具

Android XML资源合并工具

作者: Cylee1989 | 来源:发表于2019-07-09 15:26 被阅读0次

    在做聚合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 += "</" + tempTokenLocalName + ">\n"
                            }
                        }
                    }
                }
                isStart, isEnd = false, false
            }
        }
    }
    
    func (service *TService) makeManifestXML() {
        content := ""
        // 增加 manifest 标签
        content += "<manifest " + service.ManifestAttr + ">\n"
    
        // 增加 uses-sdk 标签
        if service.UsesSdkAttr != "" {
            content += "<uses-sdk " + service.UsesSdkAttr + "/>\n"
        }
    
        // 增加 supports-screens 标签
        if service.SupportsScreensAttr != "" {
            content += "<supports-screens " + service.SupportsScreensAttr + "/>\n"
        }
    
        // 增加 uses-configuration 标签
        if service.UsesConfigurationAttr != "" {
            content += "<uses-configuration " + service.UsesConfigurationAttr + "/>\n"
        }
    
        // 增加 application外层 标签
        for _, token := range service.OutApplication {
            content += "<" + token.Tag + " " + token.Attr
            if token.Data == "" {
                content += "/>\n"
            } else {
                content += ">\n"
                content += token.Data
                content += "</" + token.Tag + ">\n"
            }
        }
    
        // 增加 application 标签
        content += "<application " + service.ApplicationAttr + " >\n"
    
        // 增加 application内层 标签
        for _, token := range service.InApplication {
            content += "<" + token.Tag + " " + token.Attr
            if token.Data == "" {
                content += "/>\n"
            } else {
                content += ">\n"
                content += token.Data
                content += "</" + token.Tag + ">\n"
            }
        }
    
        content += "</application>\n"
        content += "</manifest>\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 += "</" + tokenNameLocal + ">"
                }
            case xml.CharData:
                if isSaveData {
                    content := string([]byte(token))
                    if strings.TrimSpace(content) != "" {
                        service.XMLData.Data += content
                        isHasContent = true
                    }
                }
            }
        }
    }
    
    func (service *TService) makeXML() {
        content := "<resources>"
        content += service.XMLData.Data
        content += "\n</resources>"
        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资源合并工具

          本文链接:https://www.haomeiwen.com/subject/bkbxkctx.html