fix issue: Styled strings merging. #5

This commit is contained in:
REAndroid 2023-01-07 09:32:06 -05:00
parent 8c7b70fd3a
commit ac88f79acc
5 changed files with 213 additions and 125 deletions

View File

@ -17,6 +17,8 @@ package com.reandroid.lib.apk;
import com.reandroid.archive.APKArchive; import com.reandroid.archive.APKArchive;
import com.reandroid.lib.arsc.chunk.TableBlock; import com.reandroid.lib.arsc.chunk.TableBlock;
import com.reandroid.lib.arsc.pool.TableStringPool;
import com.reandroid.lib.arsc.pool.builder.StringPoolMerger;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@ -35,8 +37,11 @@ public class ApkBundle {
if(moduleList.size()==0){ if(moduleList.size()==0){
throw new FileNotFoundException("Nothing to merge, empty modules"); throw new FileNotFoundException("Nothing to merge, empty modules");
} }
ApkModule result=new ApkModule("merged", new APKArchive()); ApkModule result=new ApkModule(generateMergedModuleName(), new APKArchive());
result.setAPKLogger(apkLogger); result.setAPKLogger(apkLogger);
mergeStringPools(result);
ApkModule base=getBaseModule(); ApkModule base=getBaseModule();
if(base==null){ if(base==null){
base=getLargestTableModule(); base=getLargestTableModule();
@ -56,6 +61,43 @@ public class ApkBundle {
result.getApkArchive().sortApkFiles(); result.getApkArchive().sortApkFiles();
return result; return result;
} }
private void mergeStringPools(ApkModule mergedModule) throws IOException {
if(!hasOneTableBlock() || mergedModule.hasTableBlock()){
return;
}
logMessage("Merging string pools ... ");
TableBlock createdTable = new TableBlock();
BlockInputSource<TableBlock> inputSource=
new BlockInputSource<>(TableBlock.FILE_NAME, createdTable);
mergedModule.getApkArchive().add(inputSource);
StringPoolMerger poolMerger = new StringPoolMerger();
for(ApkModule apkModule:getModules()){
if(!apkModule.hasTableBlock()){
continue;
}
TableStringPool stringPool = apkModule.getVolatileTableStringPool();
poolMerger.add(stringPool);
}
poolMerger.mergeTo(createdTable.getTableStringPool());
logMessage("Merged string pools="+poolMerger.getMergedPools()
+", style="+poolMerger.getMergedStyleStrings()
+", strings="+poolMerger.getMergedStrings());
}
private String generateMergedModuleName(){
Set<String> moduleNames=mModulesMap.keySet();
String merged="merged";
int i=1;
String name=merged;
while (moduleNames.contains(name)){
name=merged+"_"+i;
i++;
}
return name;
}
private ApkModule getLargestTableModule() throws IOException { private ApkModule getLargestTableModule() throws IOException {
ApkModule apkModule=null; ApkModule apkModule=null;
int chunkSize=0; int chunkSize=0;
@ -123,6 +165,14 @@ public class ApkBundle {
public Collection<ApkModule> getModules(){ public Collection<ApkModule> getModules(){
return mModulesMap.values(); return mModulesMap.values();
} }
private boolean hasOneTableBlock(){
for(ApkModule apkModule:getModules()){
if(apkModule.hasTableBlock()){
return true;
}
}
return false;
}
public void setAPKLogger(APKLogger logger) { public void setAPKLogger(APKLogger logger) {
this.apkLogger = logger; this.apkLogger = logger;
} }

View File

@ -180,7 +180,7 @@ public class TableBlock extends BaseChunk implements JSONConvert<JSONObject> {
if(tableBlock==null||tableBlock==this){ if(tableBlock==null||tableBlock==this){
return; return;
} }
if(getPackageArray().childesCount()==0){ if(getPackageArray().childesCount()==0 && getTableStringPool().countStrings()==0){
getTableStringPool().merge(tableBlock.getTableStringPool()); getTableStringPool().merge(tableBlock.getTableStringPool());
} }
getPackageArray().merge(tableBlock.getPackageArray()); getPackageArray().merge(tableBlock.getPackageArray());

View File

@ -160,7 +160,7 @@ public class StyleItem extends IntegerArray implements JSONConvert<JSONObject> {
} }
}; };
} }
final List<StyleSpanInfo> getSpanInfoList(){ public final List<StyleSpanInfo> getSpanInfoList(){
if(mSpanInfoList!=null){ if(mSpanInfoList!=null){
return mSpanInfoList; return mSpanInfoList;
} }

View File

@ -1,122 +0,0 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* 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.
*/
package com.reandroid.lib.arsc.pool.builder;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
public class SpannedText {
private String mTag;
private String mText;
private int mStart;
private int mEnd;
private List<SpannedText> mChildes=new ArrayList<>();
public SpannedText(){
}
public void parse(String text){
char[] allChars=text.toCharArray();
int len=allChars.length;
String firstTag=null;
String endTag=null;
StringBuilder tag=null;
boolean firstTagFound=false;
int posFirst=0;
int posSecond=0;
for(int i=0;i<len;i++){
char ch=allChars[i];
if(tag!=null){
tag.append(ch);
if(ch=='>'){
if(!firstTagFound){
firstTag=tag.toString();
firstTagFound=true;
tag=null;
}else {
endTag=tag.toString();
if(isTagsMatch(firstTag, endTag)){
break;
}
endTag=null;
tag=null;
continue;
}
}
continue;
}
if(ch=='<'){
if(isClosing(allChars, i+1)){
if(!firstTagFound){
tag=null;
continue;
}
}else if(firstTagFound){
firstTagFound=false;
firstTag=null;
}
tag=new StringBuilder();
tag.append(ch);
if(!firstTagFound){
posFirst=i;
}else {
posSecond=i;
}
}
}
if(firstTag==null || endTag==null){
return;
}
mStart=posFirst;
mEnd=posSecond;
StringBuilder builder=new StringBuilder();
builder.append(text, 0, posFirst);
builder.append(text, posFirst+firstTag.length(), posSecond);
builder.append(text.substring(posSecond+endTag.length()));
mText=builder.toString();
}
private boolean isClosing(char[] allChars, int pos){
for(int i=pos;i<allChars.length;i++){
char ch=allChars[i];
if(ch=='/'){
return true;
}
if(ch!=' '){
return false;
}
}
return false;
}
private boolean isTagsMatch(String start, String end){
start=trimStart(start);
end=trimEndTag(end);
return start.equals(end);
}
private String trimStart(String start){
start=start.substring(1, start.length()-1);
int i=start.indexOf(' ');
if(i>0){
start=start.substring(0,i);
}
start=start.trim();
return start;
}
private String trimEndTag(String end){
end=end.substring(1, end.length()-1).trim();
end=end.substring(1).trim();
return end;
}
private static final Pattern PATTERN_TAG=Pattern.compile("^(.*)(<[^/<>]+>)([^<]+)(</[^<>]+>)(.*)$");
}

View File

@ -0,0 +1,160 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* 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.
*/
package com.reandroid.lib.arsc.pool.builder;
import com.reandroid.lib.arsc.array.StringArray;
import com.reandroid.lib.arsc.array.StyleArray;
import com.reandroid.lib.arsc.item.StyleItem;
import com.reandroid.lib.arsc.item.TableString;
import com.reandroid.lib.arsc.model.StyleSpanInfo;
import com.reandroid.lib.arsc.pool.TableStringPool;
import java.util.*;
public class StringPoolMerger implements Comparator<String> {
private final Set<TableStringPool> mPools;
private int mMergedPools;
private int mMergedStrings;
private int mMergedStyleStrings;
public StringPoolMerger(){
this.mPools=new HashSet<>();
}
public void mergeTo(TableStringPool destination){
mMergedPools=0;
mMergedStrings=0;
mMergedStyleStrings=0;
if(destination.countStrings()>0 || destination.countStyles()>0){
throw new IllegalArgumentException("Destination string pool is not empty");
}
mergeStyledStrings(destination);
mergeNonStyledStrings(destination);
mMergedPools = mPools.size();
mPools.clear();
destination.refresh();
}
public void add(TableStringPool stringPool){
mPools.add(stringPool);
}
public int getMergedPools() {
return mMergedPools;
}
public int getMergedStyleStrings() {
return mMergedStyleStrings;
}
public int getMergedStrings() {
return mMergedStrings;
}
private void mergeStyledStrings(TableStringPool destination){
List<TableString> styledStrings = getStyledStrings();
Map<String, TableString> mapTableStrings =
destination.insertStrings(toStringList(styledStrings));
Map<String, TableString> mapTags =
destination.insertStrings(listStyleTags(styledStrings));
StyleArray styleArray = destination.getStyleArray();
styleArray.setChildesCount(styledStrings.size());
for(TableString tableString:styledStrings){
TableString createdString = mapTableStrings.get(tableString.get());
StyleItem createdStyle = styleArray.get(createdString.getIndex());
StyleItem styleItem = tableString.getStyle();
for(StyleSpanInfo spanInfo:styleItem.getSpanInfoList()){
if(spanInfo!=null && createdStyle!=null){
int tagReference = mapTags.get(spanInfo.getTag())
.getIndex();
createdStyle.addStylePiece(
tagReference,
spanInfo.getFirst(),
spanInfo.getLast());
}
}
}
mMergedStyleStrings=styledStrings.size();
}
private void mergeNonStyledStrings(TableStringPool destination){
List<String> nonStyledStrings=getNonStyledStrings();
destination.insertStrings(nonStyledStrings);
mMergedStrings=nonStyledStrings.size();
}
private List<TableString> getStyledStrings(){
Map<String, TableString> mapUniqueHtml = new HashMap<>();
for(TableStringPool pool:mPools){
int styleCount = pool.countStyles();
StringArray<TableString> stringArray = pool.getStringsArray();
for(int i=0;i<styleCount;i++){
TableString tableString = stringArray.get(i);
if(tableString==null || !tableString.hasStyle()){
continue;
}
mapUniqueHtml.put(tableString.getHtml(), tableString);
}
}
return new ArrayList<>(mapUniqueHtml.values());
}
private List<String> getNonStyledStrings(){
Set<String> uniqueSet = new HashSet<>();
for(TableStringPool pool:mPools){
TableString[] tableStrings = pool.getStrings();
if(tableStrings==null){
continue;
}
for(int i=0;i<tableStrings.length;i++){
TableString tableString=tableStrings[i];
if(tableString==null || tableString.hasStyle()){
continue;
}
uniqueSet.add(tableString.get());
}
}
List<String> results=new ArrayList<>(uniqueSet);
results.sort(this);
return results;
}
private List<String> toStringList(Collection<TableString> tableStringList){
List<String> results=new ArrayList<>(tableStringList.size());
for(TableString tableString:tableStringList){
String str=tableString.get();
if(str!=null){
results.add(str);
}
}
results.sort(this);
return results;
}
private List<String> listStyleTags(List<TableString> styledStrings){
Set<String> resultSet=new HashSet<>();
for(TableString tableString:styledStrings){
StyleItem style = tableString.getStyle();
if(style==null){
continue;
}
for(StyleSpanInfo spanInfo:style.getSpanInfoList()){
if(spanInfo!=null){
resultSet.add(spanInfo.getTag());
}
}
}
List<String> results=new ArrayList<>(resultSet);
results.sort(this);
return results;
}
@Override
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}
}