diff --git a/src/main/java/com/reandroid/arsc/item/StyleItem.java b/src/main/java/com/reandroid/arsc/item/StyleItem.java index cf80d97..dd0bbb4 100755 --- a/src/main/java/com/reandroid/arsc/item/StyleItem.java +++ b/src/main/java/com/reandroid/arsc/item/StyleItem.java @@ -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 { } public String applyHtml(String str, boolean xml){ - if(str==null){ + if(str == null){ return null; } - List spanInfoList = getSpanInfoList(); - if(isEmpty(spanInfoList)){ - return str; - } - StringBuilder builder=new StringBuilder(); - char[] allChars=str.toCharArray(); - int max=allChars.length; - for(int i=0;i'){ - builder.append(">"); - }else if(ch=='<'){ - builder.append("<"); - }else if(ch=='&'){ - builder.append("&"); - } - 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 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){ diff --git a/src/main/java/com/reandroid/arsc/model/StyledStringBuilder.java b/src/main/java/com/reandroid/arsc/model/StyledStringBuilder.java new file mode 100644 index 0000000..f736da9 --- /dev/null +++ b/src/main/java/com/reandroid/arsc/model/StyledStringBuilder.java @@ -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 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 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 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 "&"; + case '<': + return "<"; + case '>': + return ">"; + 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 firstTagList; + final char mChar; + private List 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); + } + } +}