在做聚合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 += "\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 += "" + token.Tag + ">\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 += "" + token.Tag + ">\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 += "" + 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 := ""
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!")
}