fix: encode/decode XML styled strings properly

This commit is contained in:
REAndroid 2023-01-16 10:12:13 -05:00
parent ee5db344af
commit a5d71a28a4
13 changed files with 183 additions and 55 deletions

View File

@ -25,6 +25,7 @@ import com.reandroid.lib.arsc.chunk.xml.AndroidManifestBlock;
import com.reandroid.lib.arsc.chunk.xml.ResXmlBlock; import com.reandroid.lib.arsc.chunk.xml.ResXmlBlock;
import com.reandroid.lib.arsc.container.SpecTypePair; import com.reandroid.lib.arsc.container.SpecTypePair;
import com.reandroid.lib.arsc.decoder.ValueDecoder; import com.reandroid.lib.arsc.decoder.ValueDecoder;
import com.reandroid.lib.arsc.item.TableString;
import com.reandroid.lib.arsc.value.*; import com.reandroid.lib.arsc.value.*;
import com.reandroid.lib.common.EntryStore; import com.reandroid.lib.common.EntryStore;
import com.reandroid.lib.common.Frameworks; import com.reandroid.lib.common.Frameworks;
@ -238,18 +239,17 @@ import java.util.*;
attribute.setNameId(resourceId); attribute.setNameId(resourceId);
element.setResourceId(resourceId); element.setResourceId(resourceId);
if(!entryBlock.isEntryTypeBag()){ if(!entryBlock.isEntryTypeBag()){
String value;
ResValueInt resValueInt=(ResValueInt) entryBlock.getResValue(); ResValueInt resValueInt=(ResValueInt) entryBlock.getResValue();
if(resValueInt.getValueType()== ValueType.STRING){ if(resValueInt.getValueType()== ValueType.STRING){
value=ValueDecoder.escapeSpecialCharacter( XmlHelper.setTextContent(element,
resValueInt.getValueAsString()); resValueInt.getValueAsPoolString());
}else { }else {
value= ValueDecoder.decodeEntryValue(entryStore, String value = ValueDecoder.decodeEntryValue(entryStore,
entryBlock.getPackageBlock(), entryBlock.getPackageBlock(),
resValueInt.getValueType(), resValueInt.getValueType(),
resValueInt.getData()); resValueInt.getData());
}
element.setTextContent(value); element.setTextContent(value);
}
}else { }else {
ResValueBag resValueBag=(ResValueBag) entryBlock.getResValue(); ResValueBag resValueBag=(ResValueBag) entryBlock.getResValue();
xmlBagDecoder.decode(resValueBag, element); xmlBagDecoder.decode(resValueBag, element);

View File

@ -0,0 +1,33 @@
/*
* 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.apk;
import com.reandroid.lib.arsc.item.StringItem;
import com.reandroid.xml.XMLElement;
public class XmlHelper {
public static void setTextContent(XMLElement element, StringItem stringItem){
if(stringItem==null){
element.clearChildNodes();
return;
}
if(!stringItem.hasStyle()){
element.setTextContent(stringItem.get());
}else {
element.setSpannableText(stringItem.getHtml());
}
}
}

View File

@ -16,6 +16,7 @@
package com.reandroid.lib.apk.xmldecoder; package com.reandroid.lib.apk.xmldecoder;
import com.reandroid.lib.apk.ApkUtil; import com.reandroid.lib.apk.ApkUtil;
import com.reandroid.lib.apk.XmlHelper;
import com.reandroid.lib.arsc.decoder.ValueDecoder; import com.reandroid.lib.arsc.decoder.ValueDecoder;
import com.reandroid.lib.arsc.value.ResValueBag; import com.reandroid.lib.arsc.value.ResValueBag;
import com.reandroid.lib.arsc.value.ResValueBagItem; import com.reandroid.lib.arsc.value.ResValueBagItem;
@ -38,11 +39,16 @@ import java.util.Set;
Set<ValueType> valueTypes = new HashSet<>(); Set<ValueType> valueTypes = new HashSet<>();
for(int i=0;i<bagItems.length;i++){ for(int i=0;i<bagItems.length;i++){
ResValueBagItem bagItem = bagItems[i]; ResValueBagItem bagItem = bagItems[i];
String value = ValueDecoder.decodeIntEntry(entryStore, bagItem); ValueType valueType = bagItem.getValueType();
XMLElement child = new XMLElement("item"); XMLElement child = new XMLElement("item");
if(valueType == ValueType.STRING){
XmlHelper.setTextContent(child, bagItem.getValueAsPoolString());
}else {
String value = ValueDecoder.decodeIntEntry(entryStore, bagItem);
child.setTextContent(value); child.setTextContent(value);
}
parentElement.addChild(child); parentElement.addChild(child);
valueTypes.add(bagItem.getValueType()); valueTypes.add(valueType);
} }
if(valueTypes.contains(ValueType.STRING)){ if(valueTypes.contains(ValueType.STRING)){
parentElement.setTagName(ApkUtil.TAG_STRING_ARRAY); parentElement.setTagName(ApkUtil.TAG_STRING_ARRAY);

View File

@ -15,6 +15,7 @@
*/ */
package com.reandroid.lib.apk.xmldecoder; package com.reandroid.lib.apk.xmldecoder;
import com.reandroid.lib.apk.XmlHelper;
import com.reandroid.lib.arsc.chunk.PackageBlock; import com.reandroid.lib.arsc.chunk.PackageBlock;
import com.reandroid.lib.arsc.decoder.ValueDecoder; import com.reandroid.lib.arsc.decoder.ValueDecoder;
import com.reandroid.lib.arsc.value.ResValueBag; import com.reandroid.lib.arsc.value.ResValueBag;
@ -57,11 +58,14 @@ class XMLCommonBagDecoder extends BagDecoder{
child.setAttribute("name", name); child.setAttribute("name", name);
ValueType valueType = item.getValueType();
if(valueType == ValueType.STRING){
XmlHelper.setTextContent(child, item.getValueAsPoolString());
}else {
String value = ValueDecoder.decode(entryStore, currentPackageId, String value = ValueDecoder.decode(entryStore, currentPackageId,
resourceId, item.getValueType(), item.getData()); resourceId, item.getValueType(), item.getData());
child.setTextContent(value); child.setTextContent(value);
}
parentElement.addChild(child); parentElement.addChild(child);
} }
} }

View File

@ -15,10 +15,12 @@
*/ */
package com.reandroid.lib.apk.xmldecoder; package com.reandroid.lib.apk.xmldecoder;
import com.reandroid.lib.apk.XmlHelper;
import com.reandroid.lib.arsc.decoder.ValueDecoder; import com.reandroid.lib.arsc.decoder.ValueDecoder;
import com.reandroid.lib.arsc.value.BaseResValue; import com.reandroid.lib.arsc.value.BaseResValue;
import com.reandroid.lib.arsc.value.ResValueBag; import com.reandroid.lib.arsc.value.ResValueBag;
import com.reandroid.lib.arsc.value.ResValueBagItem; import com.reandroid.lib.arsc.value.ResValueBagItem;
import com.reandroid.lib.arsc.value.ValueType;
import com.reandroid.lib.arsc.value.plurals.PluralsQuantity; import com.reandroid.lib.arsc.value.plurals.PluralsQuantity;
import com.reandroid.lib.common.EntryStore; import com.reandroid.lib.common.EntryStore;
import com.reandroid.xml.XMLElement; import com.reandroid.xml.XMLElement;
@ -36,11 +38,15 @@ class XMLPluralsDecoder extends BagDecoder{
ResValueBagItem item = bagItems[i]; ResValueBagItem item = bagItems[i];
PluralsQuantity quantity = PluralsQuantity.valueOf(item.getIdLow()); PluralsQuantity quantity = PluralsQuantity.valueOf(item.getIdLow());
String value = ValueDecoder.decodeIntEntry(entryStore, item);
XMLElement child=new XMLElement("item"); XMLElement child=new XMLElement("item");
child.setAttribute("quantity", quantity.toString()); child.setAttribute("quantity", quantity.toString());
if(item.getValueType() == ValueType.STRING){
XmlHelper.setTextContent(child, item.getValueAsPoolString());
}else {
String value = ValueDecoder.decodeIntEntry(entryStore, item);
child.setTextContent(value); child.setTextContent(value);
}
parentElement.addChild(child); parentElement.addChild(child);
} }

View File

@ -15,6 +15,7 @@
*/ */
package com.reandroid.lib.apk.xmlencoder; package com.reandroid.lib.apk.xmlencoder;
import com.reandroid.lib.apk.ApkUtil;
import com.reandroid.xml.XMLDocument; import com.reandroid.xml.XMLDocument;
import com.reandroid.xml.XMLElement; import com.reandroid.xml.XMLElement;
import com.reandroid.xml.XMLException; import com.reandroid.xml.XMLException;
@ -85,6 +86,9 @@ class ValuesEncoder {
if(type.startsWith("plurals")){ if(type.startsWith("plurals")){
return true; return true;
} }
if(type.startsWith("array")){
return true;
}
if(type.startsWith("string")){ if(type.startsWith("string")){
return false; return false;
} }
@ -108,7 +112,13 @@ class ValuesEncoder {
if(type==null){ if(type==null){
type=first.getTagName(); type=first.getTagName();
} }
if(type==null||type.equals("item")){ if(type==null){
return def;
}
if(type.endsWith("-array")){
return "array";
}
if(type.equals("item")){
return def; return def;
} }
return type; return type;

View File

@ -30,8 +30,10 @@ import java.util.*;
public class ValuesStringPoolBuilder { public class ValuesStringPoolBuilder {
private final Set<String> stringList; private final Set<String> stringList;
private final Set<String> styleList;
public ValuesStringPoolBuilder(){ public ValuesStringPoolBuilder(){
this.stringList=new HashSet<>(); this.stringList=new HashSet<>();
this.styleList=new HashSet<>();
} }
public void addTo(TableStringPool stringPool){ public void addTo(TableStringPool stringPool){
if(stringPool.getStringsArray().childesCount()==0){ if(stringPool.getStringsArray().childesCount()==0){
@ -39,6 +41,7 @@ import java.util.*;
} }
stringPool.addStrings(stringList); stringPool.addStrings(stringList);
stringList.clear(); stringList.clear();
styleList.clear();
stringPool.refresh(); stringPool.refresh();
} }
private void buildWithStyles(TableStringPool stringPool){ private void buildWithStyles(TableStringPool stringPool){
@ -74,11 +77,13 @@ import java.util.*;
private List<XMLSpannable> buildSpannable(){ private List<XMLSpannable> buildSpannable(){
List<XMLSpannable> results=new ArrayList<>(); List<XMLSpannable> results=new ArrayList<>();
Set<String> removeList=new HashSet<>(); Set<String> removeList=new HashSet<>();
for(String text:stringList){ for(String text:styleList){
XMLSpannable spannable=XMLSpannable.parse(text); XMLSpannable spannable=XMLSpannable.parse(text);
if(spannable!=null){ if(spannable!=null){
results.add(spannable); results.add(spannable);
removeList.add(text); removeList.add(text);
}else {
stringList.add(text);
} }
} }
stringList.removeAll(removeList); stringList.removeAll(removeList);
@ -143,14 +148,21 @@ import java.util.*;
} }
} }
private void addStrings(XMLElement element){ private void addStrings(XMLElement element){
if(element.hasChildElements()){
addStyleElement(element);
}else {
String text = ValueDecoder String text = ValueDecoder
.unEscapeSpecialCharacter(element.getTextContent()); .unEscapeSpecialCharacter(element.getTextContent());
addString(text); addString(text);
} }
}
private void addString(String text){ private void addString(String text){
if(text!=null && text.length()>0 && text.charAt(0)!='@'){ if(text!=null && text.length()>0 && text.charAt(0)!='@'){
stringList.add(text); stringList.add(text);
} }
} }
private void addStyleElement(XMLElement element){
styleList.add(element.buildTextContent());
}
} }

View File

@ -27,6 +27,10 @@ public abstract class BaseResValueItem extends BaseResValue implements ResValueI
BaseResValueItem(int bytesLength) { BaseResValueItem(int bytesLength) {
super(bytesLength); super(bytesLength);
} }
public TableString getValueAsPoolString(){
return getTableString(getData());
}
String getString(int ref){ String getString(int ref){
TableString tableString=getTableString(ref); TableString tableString=getTableString(ref);
if(tableString==null){ if(tableString==null){

View File

@ -62,6 +62,9 @@ public class XMLComment extends XMLElement {
return XMLUtil.isEmpty(getTextContent()); return XMLUtil.isEmpty(getTextContent());
} }
void buildTextContent(Writer writer) throws IOException{
}
@Override @Override
public boolean write(Writer writer, boolean newLineAttributes) throws IOException { public boolean write(Writer writer, boolean newLineAttributes) throws IOException {
if(isHidden()){ if(isHidden()){

View File

@ -16,6 +16,8 @@
package com.reandroid.xml; package com.reandroid.xml;
import com.reandroid.xml.parser.XMLSpanParser;
import java.io.IOException; import java.io.IOException;
import java.io.StringWriter; import java.io.StringWriter;
import java.io.Writer; import java.io.Writer;
@ -26,7 +28,7 @@ public class XMLElement extends XMLNode{
private String mTagName; private String mTagName;
private final List<XMLAttribute> mAttributes = new ArrayList<>(); private final List<XMLAttribute> mAttributes = new ArrayList<>();
private final List<XMLElement> mChildElements = new ArrayList<>(); private final List<XMLElement> mChildElements = new ArrayList<>();
private List<XMLComment> mComments; private final List<XMLComment> mComments = new ArrayList<>();
private final List<XMLText> mTexts = new ArrayList<>(); private final List<XMLText> mTexts = new ArrayList<>();
private XMLElement mParent; private XMLElement mParent;
private int mIndent; private int mIndent;
@ -196,7 +198,7 @@ public class XMLElement extends XMLNode{
mTexts.clear(); mTexts.clear();
} }
public XMLComment getCommentAt(int index){ public XMLComment getCommentAt(int index){
if(mComments==null || index<0){ if(index<0){
return null; return null;
} }
if(index>=mComments.size()){ if(index>=mComments.size()){
@ -213,17 +215,11 @@ public class XMLElement extends XMLNode{
} }
} }
private void hideComments(boolean hide){ private void hideComments(boolean hide){
if(mComments==null){
return;
}
for(XMLComment ce:mComments){ for(XMLComment ce:mComments){
ce.setHidden(hide); ce.setHidden(hide);
} }
} }
public int getCommentsCount(){ public int getCommentsCount(){
if(mComments==null){
return 0;
}
return mComments.size(); return mComments.size();
} }
public void addComments(Collection<XMLComment> commentElements){ public void addComments(Collection<XMLComment> commentElements){
@ -235,11 +231,7 @@ public class XMLElement extends XMLNode{
} }
} }
public void clearComments(){ public void clearComments(){
if(mComments==null){
return;
}
mComments.clear(); mComments.clear();
mComments=null;
} }
public void addComment(XMLComment commentElement) { public void addComment(XMLComment commentElement) {
addCommentInternal(commentElement, true); addCommentInternal(commentElement, true);
@ -248,9 +240,6 @@ public class XMLElement extends XMLNode{
if(commentElement==null){ if(commentElement==null){
return; return;
} }
if(mComments==null){
mComments=new ArrayList<>();
}
mComments.add(commentElement); mComments.add(commentElement);
commentElement.setIndent(getIndent()); commentElement.setIndent(getIndent());
commentElement.setParent(this); commentElement.setParent(this);
@ -258,8 +247,12 @@ public class XMLElement extends XMLNode{
super.addChildNodeInternal(commentElement); super.addChildNodeInternal(commentElement);
} }
} }
public void removeChildElements(){ @Override
void clearChildNodesInternal(){
super.clearChildNodesInternal();
mChildElements.clear(); mChildElements.clear();
mComments.clear();
mTexts.clear();
} }
public List<XMLAttribute> listAttributes(){ public List<XMLAttribute> listAttributes(){
return mAttributes; return mAttributes;
@ -538,33 +531,41 @@ public class XMLElement extends XMLNode{
mTag =tag; mTag =tag;
} }
public String getTextContent(){ public String getTextContent(){
return getTextContent(true);
}
public String getTextContent(boolean unEscape){
String text=buildTextContent();
if(unEscape){
text=XMLUtil.unEscapeXmlChars(text);
}
return text;
}
private String buildTextContent(){
if(!hasTextContent()){ if(!hasTextContent()){
return null; return null;
} }
StringWriter writer=new StringWriter(); return buildTextContent();
for(XMLNode child:getChildNodes()){
try {
child.write(writer, false);
} catch (IOException ignored) {
} }
public String buildTextContent(){
StringWriter writer=new StringWriter();
try {
for(XMLNode node:getChildNodes()){
node.buildTextContent(writer);
} }
writer.flush(); writer.flush();
try {
writer.close(); writer.close();
} catch (IOException ignored) { } catch (IOException ignored) {
} }
return writer.toString(); return writer.toString();
} }
void buildTextContent(Writer writer) throws IOException {
writer.write("<");
writer.write(getTagName());
appendAttributes(writer, false);
if(!hasChildNodes()){
writer.write("/>");
return;
}
writer.write('>');
for(XMLNode node:getChildNodes()){
node.buildTextContent(writer);
}
if(hasChildNodes()){
writer.write("</");
writer.write(getTagName());
writer.write('>');
}
}
private void appendTextContent(Writer writer) throws IOException { private void appendTextContent(Writer writer) throws IOException {
for(XMLNode child:getChildNodes()){ for(XMLNode child:getChildNodes()){
if(child instanceof XMLElement){ if(child instanceof XMLElement){
@ -573,6 +574,9 @@ public class XMLElement extends XMLNode{
child.write(writer, false); child.write(writer, false);
} }
} }
public boolean hasChildElements(){
return mChildElements.size()>0;
}
public boolean hasTextContent() { public boolean hasTextContent() {
return mTexts.size()>0; return mTexts.size()>0;
} }
@ -582,6 +586,17 @@ public class XMLElement extends XMLNode{
} }
return mTexts.get(0).getText(); return mTexts.get(0).getText();
} }
public void setSpannableText(String text){
clearChildNodes();
XMLElement element = parseSpanSafe(text);
if(element==null){
addText( new XMLText(text));
return;
}
for(XMLNode xmlNode:element.getChildNodes()){
super.addChildNode(xmlNode);
}
}
public void setTextContent(String text){ public void setTextContent(String text){
setTextContent(text, true); setTextContent(text, true);
} }
@ -775,4 +790,16 @@ public class XMLElement extends XMLNode{
return strWriter.toString(); return strWriter.toString();
} }
private static XMLElement parseSpanSafe(String spanText){
if(spanText==null){
return null;
}
try {
XMLSpanParser spanParser = new XMLSpanParser();
return spanParser.parse(spanText);
} catch (XMLException ignored) {
return null;
}
}
} }

View File

@ -66,8 +66,20 @@ public abstract class XMLNode {
} }
mChildNodes.remove(xmlNode); mChildNodes.remove(xmlNode);
} }
public void clearChildNodes(){
clearChildNodesInternal();
}
void clearChildNodesInternal(){
mChildNodes.clear();
}
public List<XMLNode> getChildNodes() { public List<XMLNode> getChildNodes() {
return mChildNodes; return mChildNodes;
}
boolean hasChildNodes(){
return mChildNodes.size()>0;
}
void buildTextContent(Writer writer) throws IOException{
} }
public boolean write(Writer writer) throws IOException { public boolean write(Writer writer) throws IOException {
return write(writer, false); return write(writer, false);

View File

@ -44,6 +44,10 @@ public class XMLText extends XMLNode{
this.text=XMLUtil.escapeXmlChars(text); this.text=XMLUtil.escapeXmlChars(text);
} }
@Override @Override
void buildTextContent(Writer writer) throws IOException{
writer.write(this.text);
}
@Override
public boolean write(Writer writer, boolean newLineAttributes) throws IOException { public boolean write(Writer writer, boolean newLineAttributes) throws IOException {
if(!XMLUtil.isEmpty(this.text)){ if(!XMLUtil.isEmpty(this.text)){
writer.write(this.text); writer.write(this.text);

View File

@ -15,7 +15,10 @@
*/ */
package com.reandroid.xml; package com.reandroid.xml;
public class XmlHeaderElement extends XMLElement { import java.io.IOException;
import java.io.Writer;
public class XmlHeaderElement extends XMLElement {
private static final String ATTR_VERSION="version"; private static final String ATTR_VERSION="version";
private static final String ATTR_ENCODING="encoding"; private static final String ATTR_ENCODING="encoding";
private static final String ATTR_STANDALONE="standalone"; private static final String ATTR_STANDALONE="standalone";
@ -100,4 +103,8 @@ public class XmlHeaderElement extends XMLElement {
int getIndent(){ int getIndent(){
return 0; return 0;
} }
@Override
void buildTextContent(Writer writer) throws IOException {
}
} }