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.container.SpecTypePair;
import com.reandroid.lib.arsc.decoder.ValueDecoder;
import com.reandroid.lib.arsc.item.TableString;
import com.reandroid.lib.arsc.value.*;
import com.reandroid.lib.common.EntryStore;
import com.reandroid.lib.common.Frameworks;
@ -238,18 +239,17 @@ import java.util.*;
attribute.setNameId(resourceId);
element.setResourceId(resourceId);
if(!entryBlock.isEntryTypeBag()){
String value;
ResValueInt resValueInt=(ResValueInt) entryBlock.getResValue();
if(resValueInt.getValueType()== ValueType.STRING){
value=ValueDecoder.escapeSpecialCharacter(
resValueInt.getValueAsString());
XmlHelper.setTextContent(element,
resValueInt.getValueAsPoolString());
}else {
value= ValueDecoder.decodeEntryValue(entryStore,
String value = ValueDecoder.decodeEntryValue(entryStore,
entryBlock.getPackageBlock(),
resValueInt.getValueType(),
resValueInt.getData());
element.setTextContent(value);
}
element.setTextContent(value);
}else {
ResValueBag resValueBag=(ResValueBag) entryBlock.getResValue();
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;
import com.reandroid.lib.apk.ApkUtil;
import com.reandroid.lib.apk.XmlHelper;
import com.reandroid.lib.arsc.decoder.ValueDecoder;
import com.reandroid.lib.arsc.value.ResValueBag;
import com.reandroid.lib.arsc.value.ResValueBagItem;
@ -38,11 +39,16 @@ import java.util.Set;
Set<ValueType> valueTypes = new HashSet<>();
for(int i=0;i<bagItems.length;i++){
ResValueBagItem bagItem = bagItems[i];
String value = ValueDecoder.decodeIntEntry(entryStore, bagItem);
ValueType valueType = bagItem.getValueType();
XMLElement child = new XMLElement("item");
child.setTextContent(value);
if(valueType == ValueType.STRING){
XmlHelper.setTextContent(child, bagItem.getValueAsPoolString());
}else {
String value = ValueDecoder.decodeIntEntry(entryStore, bagItem);
child.setTextContent(value);
}
parentElement.addChild(child);
valueTypes.add(bagItem.getValueType());
valueTypes.add(valueType);
}
if(valueTypes.contains(ValueType.STRING)){
parentElement.setTagName(ApkUtil.TAG_STRING_ARRAY);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,7 +15,10 @@
*/
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_ENCODING="encoding";
private static final String ATTR_STANDALONE="standalone";
@ -100,4 +103,8 @@ public class XmlHeaderElement extends XMLElement {
int getIndent(){
return 0;
}
@Override
void buildTextContent(Writer writer) throws IOException {
}
}