fix styled string builder

This commit is contained in:
REAndroid 2023-05-06 14:42:45 +02:00
parent 829b32520e
commit 29009bda13
2 changed files with 200 additions and 77 deletions

View File

@ -1,4 +1,4 @@
/*
/*
* Copyright (C) 2022 github.com/REAndroid
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -17,6 +17,7 @@ package com.reandroid.arsc.item;
import com.reandroid.arsc.io.BlockReader;
import com.reandroid.arsc.model.StyleSpanInfo;
import com.reandroid.arsc.model.StyledStringBuilder;
import com.reandroid.arsc.pool.StringPool;
import com.reandroid.json.JSONConvert;
import com.reandroid.json.JSONArray;
@ -268,84 +269,10 @@ public class StyleItem extends IntegerArray implements JSONConvert<JSONObject> {
}
public String applyHtml(String str, boolean xml){
if(str==null){
if(str == null){
return null;
}
List<StyleSpanInfo> spanInfoList = getSpanInfoList();
if(isEmpty(spanInfoList)){
return str;
}
StringBuilder builder=new StringBuilder();
char[] allChars=str.toCharArray();
int max=allChars.length;
for(int i=0;i<max;i++){
char ch=allChars[i];
boolean lastAppend=false;
for(StyleSpanInfo info:spanInfoList){
if(info==null){
continue;
}
boolean isLast=(info.getLast()==i);
if(info.getFirst()==i || isLast){
if(isLast){
builder.append(info.getEndTag());
}
if(isLast && !lastAppend){
builder.append(ch);
lastAppend=true;
}
if(!isLast) {
builder.append(info.getStartTag(xml));
}
}
}
if(!lastAppend){
if(xml){
if(isWildXml(ch) && !isEntity(allChars, i)){
if(ch=='>'){
builder.append("&gt;");
}else if(ch=='<'){
builder.append("&lt;");
}else if(ch=='&'){
builder.append("&amp;");
}
continue;
}
}
builder.append(ch);
}
}
return builder.toString();
}
private boolean isWildXml(char ch){
switch (ch){
case '&':
case '<':
case '>':
return true;
default:
return false;
}
}
private boolean isEntity(char[] chars, int offset){
if((offset+4)>=chars.length){
return false;
}
return chars[offset]=='a'
&& chars[offset+1]=='m'
&& chars[offset+2]=='p'
&& chars[offset+3]==';';
}
private boolean isEmpty(List<StyleSpanInfo> spanInfoList){
if(spanInfoList.size()==0){
return true;
}
for(StyleSpanInfo spanInfo:spanInfoList){
if(spanInfo!=null){
return false;
}
}
return true;
return StyledStringBuilder.build(str, getSpanInfoList(), xml);
}
@Override
public void setNull(boolean is_null){

View File

@ -0,0 +1,196 @@
/*
* 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.arsc.model;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class StyledStringBuilder {
public static String build(String text, Collection<StyleSpanInfo> spanInfoList, boolean xml){
if(isEmpty(spanInfoList)){
return text;
}
CharPiece[] charPieceArray = toCharPieceArray(text);
boolean spansOk = initializeTags(charPieceArray, spanInfoList, xml);
if(!spansOk){
// TODO: should throw here ?
return text;
}
if(xml){
escapeXmlChars(charPieceArray);
}
StringBuilder builder = new StringBuilder();
int length = charPieceArray.length;
for(int i = 0; i < length; i++){
CharPiece charPiece = charPieceArray[i];
charPiece.append(builder);
}
return builder.toString();
}
private static boolean isEmpty(Collection<StyleSpanInfo> spanInfoList){
if(spanInfoList == null || spanInfoList.size()==0){
return true;
}
for(StyleSpanInfo spanInfo:spanInfoList){
if(spanInfo != null){
return false;
}
}
return true;
}
private static boolean initializeTags(CharPiece[] charPieceArray, Collection<StyleSpanInfo> spanInfoList, boolean xml){
for(StyleSpanInfo spanInfo : spanInfoList){
if(spanInfo == null){
continue;
}
boolean spanOk = initializeTag(charPieceArray, spanInfo, xml);
if(!spanOk){
return false;
}
}
return true;
}
private static boolean initializeTag(CharPiece[] charPieceArray, StyleSpanInfo spanInfo, boolean xml){
int length = charPieceArray.length;
int pos = spanInfo.getFirst();
if(pos < 0 || pos >= length){
return false;
}
CharPiece charPiece = charPieceArray[pos];
charPiece.addFirstTag(spanInfo.getStartTag(xml));
pos = spanInfo.getLast();
if(pos < 0 || pos >= length){
return false;
}
charPiece = charPieceArray[pos];
charPiece.addLastTag(spanInfo.getEndTag());
return true;
}
private static void escapeXmlChars(CharPiece[] charPieceArray){
int length = charPieceArray.length;
for(int i = 0; i < length; i++){
CharPiece charPiece = charPieceArray[i];
if(isSpecialXmlChar(charPiece.mChar) && !isAlreadyEscaped(charPieceArray, i)){
charPiece.escapedXml = escapeXmlChar(charPiece.mChar);
}
}
}
private static boolean isAlreadyEscaped(CharPiece[] charPieceArray, int position){
if(charPieceArray[position].mChar != '&'){
return false;
}
if((position + 3) >= charPieceArray.length){
return false;
}
if(charPieceArray[position + 3].mChar == ';'){
char ch = charPieceArray[position + 1].mChar;
return charPieceArray[position + 2].mChar == 't'
&& (ch == 'l' || ch == 'g') ;
}
if((position + 4) >= charPieceArray.length){
return false;
}
if(charPieceArray[position + 4].mChar == ';'){
return charPieceArray[position + 1].mChar == 'a'
&& charPieceArray[position + 2].mChar == 'm'
&& charPieceArray[position + 3].mChar == 'p';
}
return false;
}
private static String escapeXmlChar(char ch){
switch (ch){
case '&':
return "&amp;";
case '<':
return "&lt;";
case '>':
return "&gt;";
default:
throw new IllegalArgumentException("Not special xml char: '" + ch + "'");
}
}
private static boolean isSpecialXmlChar(char ch){
switch (ch){
case '&':
case '<':
case '>':
return true;
default:
return false;
}
}
private static CharPiece[] toCharPieceArray(String text){
char[] chars = text.toCharArray();
int length = chars.length;
CharPiece[] results = new CharPiece[length];
for(int i = 0; i < length; i++){
results[i] = new CharPiece(i, chars[i]);
}
return results;
}
static class CharPiece{
final int position;
private List<String> firstTagList;
final char mChar;
private List<String> lastTagList;
String escapedXml;
CharPiece(int position, char ch){
this.position = position;
this.mChar = ch;
}
void append(StringBuilder builder){
if(firstTagList != null){
for(String tag : firstTagList){
builder.append(tag);
}
}
if(escapedXml != null){
builder.append(escapedXml);
}else {
builder.append(mChar);
}
if(lastTagList != null){
for(String tag : lastTagList){
builder.append(tag);
}
}
}
void addFirstTag(String tag){
if(tag == null){
return;
}
if(this.firstTagList == null){
this.firstTagList = new ArrayList<>(2);
}
this.firstTagList.add(tag);
}
void addLastTag(String tag){
if(tag == null){
return;
}
if(this.lastTagList == null){
this.lastTagList = new ArrayList<>(2);
}
this.lastTagList.add(0, tag);
}
}
}