create styled strings parser

This commit is contained in:
REAndroid 2023-01-06 13:14:49 -05:00
parent 472eb1c46c
commit c49b04cc1b
14 changed files with 624 additions and 466 deletions

View File

@ -22,6 +22,15 @@ package com.reandroid.lib.apk.xmlencoder;
import java.util.regex.Pattern; import java.util.regex.Pattern;
public class EncodeUtil { public class EncodeUtil {
public static void sortStrings(List<String> stringList){
Comparator<String> cmp=new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}
};
stringList.sort(cmp);
}
public static void sortPublicXml(List<File> fileList){ public static void sortPublicXml(List<File> fileList){
Comparator<File> cmp=new Comparator<File>() { Comparator<File> cmp=new Comparator<File>() {
@Override @Override

View File

@ -15,23 +15,75 @@
*/ */
package com.reandroid.lib.apk.xmlencoder; package com.reandroid.lib.apk.xmlencoder;
import com.reandroid.lib.arsc.array.StyleArray;
import com.reandroid.lib.arsc.decoder.ValueDecoder; import com.reandroid.lib.arsc.decoder.ValueDecoder;
import com.reandroid.lib.arsc.item.StyleItem;
import com.reandroid.lib.arsc.item.TableString;
import com.reandroid.lib.arsc.pool.TableStringPool; import com.reandroid.lib.arsc.pool.TableStringPool;
import com.reandroid.xml.XMLDocument; import com.reandroid.xml.XMLDocument;
import com.reandroid.xml.XMLElement; import com.reandroid.xml.XMLElement;
import com.reandroid.xml.XMLSpanInfo;
import com.reandroid.xml.XMLSpannable;
import java.io.File; import java.io.File;
import java.util.HashSet; import java.util.*;
import java.util.Set;
public class ValuesStringPoolBuilder { public class ValuesStringPoolBuilder {
private final Set<String> stringList; private final Set<String> stringList;
public ValuesStringPoolBuilder(){ public ValuesStringPoolBuilder(){
this.stringList=new HashSet<>(); this.stringList=new HashSet<>();
} }
public void addTo(TableStringPool stringPool){ public void addTo(TableStringPool stringPool){
if(stringPool.getStringsArray().childesCount()==0){
buildWithStyles(stringPool);
}
stringPool.addStrings(stringList); stringPool.addStrings(stringList);
stringList.clear(); stringList.clear();
stringPool.refresh();
}
private void buildWithStyles(TableStringPool stringPool){
List<XMLSpannable> spannableList = buildSpannable();
if(spannableList.size()==0){
return;
}
Map<String, TableString> stringsMap = stringPool
.insertStrings(XMLSpannable.toTextList(spannableList));
List<String> tagList =
new ArrayList<>(XMLSpannable.tagList(spannableList));
EncodeUtil.sortStrings(tagList);
Map<String, TableString> tagsMap =
stringPool.insertStrings(tagList);
StyleArray styleArray = stringPool.getStyleArray();
styleArray.setChildesCount(stringsMap.size());
for(XMLSpannable spannable:spannableList){
TableString tableString=stringsMap.get(spannable.getText());
StyleItem styleItem = styleArray.get(tableString.getIndex());
for(XMLSpanInfo spanInfo:spannable.getSpanInfoList()){
int tagRef=tagsMap.get(spanInfo.tag).getIndex();
styleItem.addStylePiece(tagRef, spanInfo.start, spanInfo.end);
}
}
stringPool.refreshUniqueIdMap();
}
private List<XMLSpannable> buildSpannable(){
List<XMLSpannable> results=new ArrayList<>();
Set<String> removeList=new HashSet<>();
for(String text:stringList){
XMLSpannable spannable=XMLSpannable.parse(text);
if(spannable!=null){
results.add(spannable);
removeList.add(text);
}
}
stringList.removeAll(removeList);
XMLSpannable.sort(results);
return results;
} }
public void scanValuesDirectory(File dir){ public void scanValuesDirectory(File dir){
addStringsFile(new File(dir, "strings.xml")); addStringsFile(new File(dir, "strings.xml"));

View File

@ -102,9 +102,9 @@ public abstract class BaseStringPool<T extends StringItem> extends BaseChunk imp
} }
List<String> sortedList=new ArrayList<>(stringList); List<String> sortedList=new ArrayList<>(stringList);
sortedList.sort(this); sortedList.sort(this);
insertStrings(sortedList); insertStringList(sortedList);
} }
private void insertStrings(List<String> stringList){ private void insertStringList(List<String> stringList){
StringArray<T> stringsArray = getStringsArray(); StringArray<T> stringsArray = getStringsArray();
int initialSize=stringsArray.childesCount(); int initialSize=stringsArray.childesCount();
stringsArray.ensureSize(initialSize + stringList.size()); stringsArray.ensureSize(initialSize + stringList.size());
@ -117,6 +117,23 @@ public abstract class BaseStringPool<T extends StringItem> extends BaseChunk imp
} }
refreshUniqueIdMap(); refreshUniqueIdMap();
} }
public Map<String, T> insertStrings(List<String> stringList){
Map<String, T> results=new HashMap<>();
StringArray<T> stringsArray = getStringsArray();
int initialSize=stringsArray.childesCount();
stringsArray.ensureSize(initialSize + stringList.size());
int size=stringsArray.childesCount();
int j=0;
for (int i=initialSize;i<size;i++){
T item=stringsArray.get(i);
String str=stringList.get(j);
item.set(str);
results.put(str, item);
j++;
}
refreshUniqueIdMap();
return results;
}
// call this after modifying string values // call this after modifying string values
public void refreshUniqueIdMap(){ public void refreshUniqueIdMap(){
mUniqueMap.clear(); mUniqueMap.clear();

View File

@ -20,20 +20,15 @@ import java.io.IOException;
import java.io.StringWriter; import java.io.StringWriter;
import java.io.Writer; import java.io.Writer;
public class XMLAttribute { public class XMLAttribute extends XMLNode{
private int mNameId; private int mNameId;
private int mValueId; private int mValueId;
private Object mValueTag;
private Object mNameTag;
private String mName; private String mName;
private String mValue; private String mValue;
public XMLAttribute(String name, String val){ public XMLAttribute(String name, String val){
mName=name; mName=name;
mValue= XMLUtil.escapeXmlChars(val); mValue= XMLUtil.escapeXmlChars(val);
} }
public void setNameId(String id){
setNameId(XMLUtil.hexToInt(id,0));
}
public void setNameId(int id){ public void setNameId(int id){
mNameId=id; mNameId=id;
} }
@ -55,18 +50,6 @@ public class XMLAttribute {
baseAttr.setValueId(getValueId()); baseAttr.setValueId(getValueId());
return baseAttr; return baseAttr;
} }
public Object getValueTag(){
return mValueTag;
}
public void setValueTag(Object obj){
mValueTag =obj;
}
public Object geNameTag(){
return mNameTag;
}
public void setNameTag(Object obj){
mNameTag =obj;
}
public String getName(){ public String getName(){
return mName; return mName;
} }
@ -117,19 +100,9 @@ public class XMLAttribute {
public boolean isEmpty(){ public boolean isEmpty(){
return XMLUtil.isEmpty(getName()); return XMLUtil.isEmpty(getName());
} }
@Override @Override
public boolean equals(Object obj){ public boolean write(Writer writer, boolean newLineAttributes) throws IOException {
if(obj instanceof XMLAttribute){
XMLAttribute attr=(XMLAttribute)obj;
if(isEmpty()){
return attr.isEmpty();
}
String s=toString();
return s.equals(attr.toString());
}
return false;
}
public boolean write(Writer writer) throws IOException {
if(isEmpty()){ if(isEmpty()){
return false; return false;
} }
@ -143,16 +116,40 @@ public class XMLAttribute {
return true; return true;
} }
@Override @Override
public String toString(){ public String toText(int indent, boolean newLineAttributes) {
if(isEmpty()){ if(isEmpty()){
return null; return null;
} }
StringWriter writer=new StringWriter(); StringWriter writer=new StringWriter();
try { try {
write(writer); write(writer);
} catch (IOException e) { } catch (IOException ignored) {
} }
writer.flush(); writer.flush();
return writer.toString(); return writer.toString();
} }
@Override
public int hashCode(){
String name=getName();
if(name==null){
name="";
}
name=getClass().getName()+name;
return name.hashCode();
}
@Override
public boolean equals(Object obj){
if(obj instanceof XMLAttribute){
XMLAttribute attr=(XMLAttribute)obj;
if(isEmpty()){
return attr.isEmpty();
}
return getName().equals(attr.getName());
}
return false;
}
@Override
public String toString(){
return toText();
}
} }

View File

@ -31,15 +31,7 @@ public class XMLComment extends XMLElement {
super(); super();
initializeStartEnd(); initializeStartEnd();
} }
@Override
XMLElement onCloneElement(){
XMLComment ce=new XMLComment(getCommentText());
ce.setHidden(isHidden());
return ce;
}
@Override
void cloneAllAttributes(XMLElement element){
}
public void setHidden(boolean hide){ public void setHidden(boolean hide){
mIsHidden=hide; mIsHidden=hide;
} }

View File

@ -20,7 +20,7 @@ import java.io.*;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Comparator; import java.util.Comparator;
public class XMLDocument { public class XMLDocument extends XMLNode{
private XMLElement mDocumentElement; private XMLElement mDocumentElement;
private Object mTag; private Object mTag;
private String mName; private String mName;
@ -189,6 +189,7 @@ public class XMLDocument {
writer.close(); writer.close();
return result; return result;
} }
@Override
public boolean write(Writer writer, boolean newLineAttributes) throws IOException{ public boolean write(Writer writer, boolean newLineAttributes) throws IOException{
boolean has_header=appendDocumentAttribute(writer); boolean has_header=appendDocumentAttribute(writer);
if(has_header){ if(has_header){
@ -196,12 +197,7 @@ public class XMLDocument {
} }
return appendDocumentElement(writer, newLineAttributes); return appendDocumentElement(writer, newLineAttributes);
} }
public String toText(){ @Override
return toText(1, false);
}
public String toText(boolean newLineAttributes){
return toText(1, newLineAttributes);
}
public String toText(int indent, boolean newLineAttributes){ public String toText(int indent, boolean newLineAttributes){
StringWriter writer=new StringWriter(); StringWriter writer=new StringWriter();
setIndent(indent); setIndent(indent);

View File

@ -21,20 +21,19 @@ import java.io.StringWriter;
import java.io.Writer; import java.io.Writer;
import java.util.*; import java.util.*;
public class XMLElement { public class XMLElement extends XMLNode{
static final long DEBUG_TO_STRING=500; static final long DEBUG_TO_STRING=500;
private String mTagName; private String mTagName;
private XMLTextAttribute mTextAttribute; private XMLTextAttribute mTextAttribute;
private List<XMLAttribute> mAttributes; private final List<XMLAttribute> mAttributes = new ArrayList<>();
private List<XMLElement> mChildes; private final List<XMLElement> mChildes = new ArrayList<>();
private List<XMLComment> mComments; private List<XMLComment> mComments;
private final List<XMLText> mTexts = new ArrayList<>();
private XMLElement mParent; private XMLElement mParent;
private int mIndent; private int mIndent;
private Object mTag; private Object mTag;
private int mResId; private int mResId;
private float mIndentScale; private float mIndentScale;
private String mUniqueAttrName;
private List<String> mUniqueNameValues;
private String mStart; private String mStart;
private String mStartPrefix; private String mStartPrefix;
private String mEnd; private String mEnd;
@ -48,6 +47,19 @@ public class XMLElement {
setDefaultStartEnd(); setDefaultStartEnd();
} }
public void addText(XMLText text){
if(text==null){
return;
}
mTexts.add(text);
super.addChildNode(text);
}
private void appendText(String text){
if(text==null || text.length()==0){
return;
}
addText(new XMLText(text));
}
public String getTagNamePrefix(){ public String getTagNamePrefix(){
int i=mTagName.indexOf(":"); int i=mTagName.indexOf(":");
if(i>0){ if(i>0){
@ -68,45 +80,6 @@ public class XMLElement {
this.mStartPrefix="/"; this.mStartPrefix="/";
this.mEndPrefix="/"; this.mEndPrefix="/";
} }
public XMLElement removeChild(XMLElement element){
if(mChildes==null || element==null){
return null;
}
int i=mChildes.indexOf(element);
if(i<0){
return null;
}
XMLElement result=mChildes.get(i);
mChildes.remove(i);
result.setParent(null);
return result;
}
public XMLElement getFirstChildWithAttrName(String name){
if(mChildes==null || name==null){
return null;
}
for(XMLElement element:mChildes){
XMLAttribute attr=element.getAttribute(name);
if(attr!=null){
return element;
}
}
return null;
}
public XMLElement getFirstChildWithAttr(String attrName, String attrValue){
if(mChildes==null || attrName==null || attrValue==null){
return null;
}
for(XMLElement element:mChildes){
XMLAttribute attr=element.getAttribute(attrName);
if(attr!=null){
if(attrValue.equals(attr.getValue())){
return element;
}
}
}
return null;
}
public void applyNameSpaceItems(){ public void applyNameSpaceItems(){
if(nameSpaceItems!=null){ if(nameSpaceItems!=null){
for(NameSpaceItem nsItem:nameSpaceItems){ for(NameSpaceItem nsItem:nameSpaceItems){
@ -162,82 +135,24 @@ public class XMLElement {
} }
return null; return null;
} }
public void setStart(String start) { void setStart(String start) {
this.mStart = start; this.mStart = start;
} }
public void setEnd(String end) { void setEnd(String end) {
this.mEnd = end; this.mEnd = end;
} }
public void setStartPrefix(String pfx) { void setStartPrefix(String pfx) {
if(pfx==null){ if(pfx==null){
pfx=""; pfx="";
} }
this.mStartPrefix = pfx; this.mStartPrefix = pfx;
} }
public void setEndPrefix(String pfx) { void setEndPrefix(String pfx) {
if(pfx==null){ if(pfx==null){
pfx=""; pfx="";
} }
this.mEndPrefix = pfx; this.mEndPrefix = pfx;
} }
public void setUniqueAttrName(String attrName){
clearUniqueNameValues();
if(XMLUtil.isEmpty(attrName)){
mUniqueAttrName=null;
return;
}
mUniqueAttrName=attrName;
}
public String getUniqueAttrName(){
return mUniqueAttrName;
}
private void clearUniqueNameValues(){
if(mUniqueNameValues!=null){
mUniqueNameValues.clear();
mUniqueNameValues=null;
}
}
private void addUniqueNameValues(XMLElement element){
if(element==null){
return;
}
XMLAttribute baseAttr=element.getAttribute(getUniqueAttrName());
if(baseAttr==null){
return;
}
addUniqueNameValues(baseAttr.getValue());
}
private void addUniqueNameValues(String nameVal){
if(XMLUtil.isEmpty(nameVal)){
return;
}
if(mUniqueNameValues==null){
mUniqueNameValues=new ArrayList<>();
}
mUniqueNameValues.add(nameVal);
}
private boolean shouldCheckUniqueElement(){
return mUniqueAttrName!=null;
}
private boolean containsUniqueNameValue(XMLElement element){
if(element==null){
return false;
}
return containsUniqueNameValue(element.getAttribute(getUniqueAttrName()));
}
private boolean containsUniqueNameValue(XMLAttribute baseAttr){
if(baseAttr==null){
return false;
}
return containsUniqueNameValue(baseAttr.getValue());
}
private boolean containsUniqueNameValue(String nameVal){
if(mUniqueNameValues==null|| XMLUtil.isEmpty(nameVal)){
return false;
}
return mUniqueNameValues.contains(nameVal);
}
void setIndentScale(float scale){ void setIndentScale(float scale){
mIndentScale=scale; mIndentScale=scale;
} }
@ -254,82 +169,11 @@ public class XMLElement {
public void setResourceId(int id){ public void setResourceId(int id){
mResId=id; mResId=id;
} }
public boolean containsSameChild(XMLElement baseElement){
if(baseElement==null||mChildes==null){
return false;
}
for(XMLElement ch:mChildes){
if(baseElement.isSame(ch)){
return true;
}
}
return false;
}
private String getUniqueId(){
StringBuilder builder=new StringBuilder(getTagName());
builder.append(attributesToString(false));
return builder.toString();
}
private boolean isSame(XMLElement baseElement){
if(baseElement==null){
return false;
}
String s1=getUniqueId();
String s2=baseElement.getUniqueId();
return XMLUtil.isStringEqual(s1,s2);
}
public XMLElement cloneElement(){
XMLElement baseElement=onCloneElement();
baseElement.setTag(getTag());
cloneAllAttributes(baseElement);
if(mChildes!=null){
for(XMLElement element:mChildes){
baseElement.addChildNoCheck(element.cloneElement());
}
}
if(mComments!=null){
for(XMLComment ce:mComments){
baseElement.addComment((XMLComment) ce.cloneElement());
}
}
return baseElement;
}
void cloneAllAttributes(XMLElement element){
if(mAttributes!=null){
for(XMLAttribute attr:mAttributes){
element.addAttributeNoCheck(attr.cloneAttr());
}
}
XMLTextAttribute textAttribute=mTextAttribute;
if(textAttribute!=null){
element.mTextAttribute=mTextAttribute.cloneAttr();
}
}
XMLElement onCloneElement(){
return new XMLElement(getTagName());
}
public XMLElement createElement(String tag) { public XMLElement createElement(String tag) {
XMLElement baseElement=new XMLElement(tag); XMLElement baseElement=new XMLElement(tag);
addChildNoCheck(baseElement); addChildNoCheck(baseElement);
return baseElement; return baseElement;
} }
public boolean containsChild(XMLElement baseElement){
if(baseElement==null||mChildes==null){
return false;
}
return mChildes.contains(baseElement);
}
public void addChild(XMLElement[] elements) {
if(elements==null){
return;
}
int max=elements.length;
for(int i=0;i<max;i++){
XMLElement baseElement=elements[i];
addChild(baseElement);
}
}
public void addChild(Collection<XMLElement> elements) { public void addChild(Collection<XMLElement> elements) {
if(elements==null){ if(elements==null){
return; return;
@ -338,8 +182,14 @@ public class XMLElement {
addChild(element); addChild(element);
} }
} }
public void addChild(XMLElement baseElement) { public void addChild(XMLElement child) {
addChildNoCheck(baseElement); addChildNoCheck(child);
}
private void clearChildElements(){
mChildes.clear();
}
private void clearTexts(){
mTexts.clear();
} }
public XMLComment getCommentAt(int index){ public XMLComment getCommentAt(int index){
if(mComments==null || index<0){ if(mComments==null || index<0){
@ -352,7 +202,7 @@ public class XMLElement {
} }
public void hideComments(boolean recursive, boolean hide){ public void hideComments(boolean recursive, boolean hide){
hideComments(hide); hideComments(hide);
if(recursive && mChildes!=null){ if(recursive){
for(XMLElement child:mChildes){ for(XMLElement child:mChildes){
child.hideComments(recursive, hide); child.hideComments(recursive, hide);
} }
@ -397,75 +247,30 @@ public class XMLElement {
mComments.add(commentElement); mComments.add(commentElement);
commentElement.setIndent(getIndent()); commentElement.setIndent(getIndent());
commentElement.setParent(this); commentElement.setParent(this);
super.addChildNode(commentElement);
} }
public void removeAllChildes(){ public void removeChildElements(){
if(mChildes==null){
return;
}
mChildes.clear(); mChildes.clear();
} }
public int getChildesCount(){ public List<XMLAttribute> listAttributes(){
if(mChildes==null){ return mAttributes;
return 0;
} }
public int getChildesCount(){
return mChildes.size(); return mChildes.size();
} }
public XMLElement getFirstElementByTagName(String tagName){ public List<XMLElement> listChildElements(){
if(tagName==null||mChildes==null){ return mChildes;
return null;
}
for(XMLElement element:mChildes){
if(tagName.equals(element.getTagName())){
return element;
}
}
return null;
}
public XMLElement[] getElementsByTagName(String tagName){
if(tagName==null||mChildes==null){
return null;
}
List<XMLElement> results=new ArrayList<>();
for(XMLElement element:mChildes){
if(tagName.equals(element.getTagName())){
results.add(element);
}
}
int max=results.size();
if(max==0){
return null;
}
return results.toArray(new XMLElement[max]);
}
public XMLElement[] getAllChildes(){
if(mChildes==null){
return null;
}
int max=mChildes.size();
if(max==0){
return null;
}
return mChildes.toArray(new XMLElement[max]);
} }
public XMLElement getChildAt(int index){ public XMLElement getChildAt(int index){
if(mChildes==null||index<0){ if(index<0 || index>=mChildes.size()){
return null;
}
if(index>=mChildes.size()){
return null; return null;
} }
return mChildes.get(index); return mChildes.get(index);
} }
public int getAttributeCount(){ public int getAttributeCount(){
if(mAttributes==null){
return 0;
}
return mAttributes.size(); return mAttributes.size();
} }
public XMLAttribute getAttributeAt(int index){ public XMLAttribute getAttributeAt(int index){
if(mAttributes==null||index<0){
return null;
}
if(index>=mAttributes.size()){ if(index>=mAttributes.size()){
return null; return null;
} }
@ -514,9 +319,6 @@ public class XMLElement {
return attr.getValueBool(); return attr.getValueBool();
} }
public XMLAttribute getAttribute(String name){ public XMLAttribute getAttribute(String name){
if(mAttributes==null){
return null;
}
if(XMLUtil.isEmpty(name)){ if(XMLUtil.isEmpty(name)){
return null; return null;
} }
@ -585,42 +387,22 @@ public class XMLElement {
if(exist!=null){ if(exist!=null){
return; return;
} }
if(mAttributes==null){
mAttributes=new ArrayList<>();
}
mAttributes.add(attr); mAttributes.add(attr);
} }
private void addAttributeNoCheck(XMLAttribute attr){ private void addAttributeNoCheck(XMLAttribute attr){
if(attr==null){ if(attr==null || attr.isEmpty()){
return; return;
} }
if(XMLUtil.isEmpty(attr.getName())){
return;
}
if(mAttributes==null){
mAttributes=new ArrayList<>();
}
mAttributes.add(attr); mAttributes.add(attr);
} }
public void sortChildes(Comparator<XMLElement> comparator){ public void sortChildes(Comparator<XMLElement> comparator){
if(mChildes==null||comparator==null){ if(comparator==null){
return; return;
} }
mChildes.sort(comparator); mChildes.sort(comparator);
} }
public void sortAttributesWithChildes(Comparator<XMLAttribute> comparator){
if(comparator==null){
return;
}
sortAttributes(comparator);
if(mChildes!=null){
for(XMLElement element:mChildes){
element.sortAttributesWithChildes(comparator);
}
}
}
public void sortAttributes(Comparator<XMLAttribute> comparator){ public void sortAttributes(Comparator<XMLAttribute> comparator){
if(mAttributes==null || comparator==null){ if(comparator==null){
return; return;
} }
mAttributes.sort(comparator); mAttributes.sort(comparator);
@ -631,16 +413,14 @@ public class XMLElement {
void setParent(XMLElement baseElement){ void setParent(XMLElement baseElement){
mParent=baseElement; mParent=baseElement;
} }
private void addChildNoCheck(XMLElement baseElement){ private void addChildNoCheck(XMLElement child){
if(baseElement==null){ if(child==null || child == this){
return; return;
} }
if(mChildes==null){ child.setParent(this);
mChildes=new ArrayList<>(); child.setIndent(getChildIndent());
} mChildes.add(child);
baseElement.setParent(this); super.addChildNode(child);
baseElement.setIndent(getChildIndent());
mChildes.add(baseElement);
} }
public int getLevel(){ public int getLevel(){
int rs=0; int rs=0;
@ -652,10 +432,13 @@ public class XMLElement {
return rs; return rs;
} }
int getIndent(){ int getIndent(){
if(hasTextContent()){
return 0;
}
return mIndent; return mIndent;
} }
int getChildIndent(){ int getChildIndent(){
if(mIndent<=0){ if(mIndent<=0 || hasTextContent()){
return 0; return 0;
} }
int rs=mIndent+1; int rs=mIndent+1;
@ -671,11 +454,9 @@ public class XMLElement {
} }
public void setIndent(int indent){ public void setIndent(int indent){
mIndent=indent; mIndent=indent;
if(mChildes!=null){
int chIndent=getChildIndent(); int chIndent=getChildIndent();
for(XMLElement be:mChildes){ for(XMLElement child:mChildes){
be.setIndent(chIndent); child.setIndent(chIndent);
}
} }
if(mComments!=null){ if(mComments!=null){
for(XMLComment ce:mComments){ for(XMLComment ce:mComments){
@ -683,22 +464,6 @@ public class XMLElement {
} }
} }
} }
private String getAttributesIndentText(){
int i=getLevel()+1;
String tagName=getTagName();
if(tagName!=null){
i+=tagName.length();
}
if(i>12){
i=12;
}
tagName="";
while (tagName.length()<i){
tagName+=" ";
}
String rs=getIndentText();
return rs+tagName;
}
private boolean appendAttributesIndentText(Writer writer) throws IOException { private boolean appendAttributesIndentText(Writer writer) throws IOException {
int i=0; int i=0;
String tagName=getTagName(); String tagName=getTagName();
@ -770,18 +535,32 @@ public class XMLElement {
mTag =tag; mTag =tag;
} }
public String getTextContent(){ public String getTextContent(){
return getTextContent(false); return getTextContent(true);
} }
public String getTextContent(boolean unEscape){ public String getTextContent(boolean unEscape){
XMLTextAttribute textAttribute= getTextAttr(); String text=buildTextContent();
return textAttribute.getText(unEscape); if(unEscape){
text=XMLUtil.unEscapeXmlChars(text);
} }
public XMLTextAttribute getTextAttribute(){ return text;
String txt=getTextContent(); }
if(txt==null){ private String buildTextContent(){
if(!hasTextContent()){
return null; return null;
} }
return mTextAttribute; StringWriter writer=new StringWriter();
for(XMLNode child:getChildNodes()){
try {
child.write(writer, false);
} catch (IOException ignored) {
}
}
writer.flush();
try {
writer.close();
} catch (IOException ignored) {
}
return writer.toString();
} }
XMLTextAttribute getTextAttr(){ XMLTextAttribute getTextAttr(){
if(mTextAttribute==null){ if(mTextAttribute==null){
@ -789,43 +568,27 @@ public class XMLElement {
} }
return mTextAttribute; return mTextAttribute;
} }
private boolean appendTextContent(Writer writer) throws IOException { private void appendTextContent(Writer writer) throws IOException {
if(mTextAttribute==null){ for(XMLNode child:getChildNodes()){
return false; if(child instanceof XMLElement){
((XMLElement)child).setIndent(0);
} }
return mTextAttribute.write(writer); child.write(writer, false);
}
private boolean hasTextContent() {
if(mTextAttribute==null){
return false;
}
return !mTextAttribute.isEmpty();
}
private void appendText(String text){
String old= getTextContent();
if (old!=null){
if(text!=null){
setTextContent(old+text);
return;
} }
} }
setTextContent(text); public boolean hasTextContent() {
return mTexts.size()>0;
} }
public XMLAttribute setTextContent(String text){ public void setTextContent(String text){
return setTextContent(text, true); setTextContent(text, true);
} }
public XMLAttribute setTextContent(String text, boolean escape){ public void setTextContent(String text, boolean escape){
XMLTextAttribute textAttribute= getTextAttr(); clearChildElements();
if(XMLUtil.isEmpty(text)){ clearTexts();
textAttribute.setText(null);
//textAttribute.setText(text);
}else {
if(escape){ if(escape){
text= XMLUtil.escapeXmlChars(text); text=XMLUtil.escapeXmlChars(text);
} }
textAttribute.setText(text); appendText(text);
}
return textAttribute;
} }
private boolean appendAttributes(Writer writer, boolean newLineAttributes) throws IOException { private boolean appendAttributes(Writer writer, boolean newLineAttributes) throws IOException {
if(mAttributes==null){ if(mAttributes==null){
@ -851,50 +614,24 @@ public class XMLElement {
} }
return addedOnce; return addedOnce;
} }
private String attributesToString(boolean newLineAttributes){
if(mAttributes==null){
return null;
}
StringBuilder builder=new StringBuilder();
boolean addedOnce=false;
for(XMLAttribute attr:mAttributes){
if(attr.isEmpty()){
continue;
}
if(addedOnce){
if(newLineAttributes){
builder.append("\n");
builder.append(getAttributesIndentText());
}else{
builder.append(" ");
}
}
builder.append(attr.toString());
addedOnce=true;
}
if(addedOnce){
return builder.toString();
}
return null;
}
boolean isEmpty(){ boolean isEmpty(){
if(mTagName!=null){ if(mTagName!=null){
return false; return false;
} }
if(mAttributes!=null && mAttributes.size()>0){ if(mAttributes.size()>0){
return false; return false;
} }
if(mComments!=null && mComments.size()>0){ if(mComments!=null && mComments.size()>0){
return false; return false;
} }
return getTextContent()==null; if(mTexts.size()>0){
}
private boolean canAppendChildes(){
if(mChildes==null){
return false; return false;
} }
for(XMLElement be:mChildes){ return true;
if (!be.isEmpty()){ }
private boolean canAppendChildes(){
for(XMLElement child:mChildes){
if (!child.isEmpty()){
return true; return true;
} }
} }
@ -921,22 +658,19 @@ public class XMLElement {
return addedOnce; return addedOnce;
} }
private boolean appendChildes(Writer writer, boolean newLineAttributes) throws IOException { private boolean appendChildes(Writer writer, boolean newLineAttributes) throws IOException {
if(mChildes==null){
return false;
}
boolean appendPrevious=true; boolean appendPrevious=true;
boolean addedOnce=false; boolean addedOnce=false;
for(XMLElement be:mChildes){ for(XMLElement child:mChildes){
if(stopWriting(writer)){ if(stopWriting(writer)){
break; break;
} }
if(be.isEmpty()){ if(child.isEmpty()){
continue; continue;
} }
if(appendPrevious){ if(appendPrevious){
writer.write(XMLUtil.NEW_LINE); writer.write(XMLUtil.NEW_LINE);
} }
appendPrevious=be.write(writer, newLineAttributes); appendPrevious=child.write(writer, newLineAttributes);
if(!addedOnce && appendPrevious){ if(!addedOnce && appendPrevious){
addedOnce=true; addedOnce=true;
} }
@ -954,6 +688,7 @@ public class XMLElement {
} }
return false; return false;
} }
@Override
public boolean write(Writer writer, boolean newLineAttributes) throws IOException { public boolean write(Writer writer, boolean newLineAttributes) throws IOException {
if(isEmpty()){ if(isEmpty()){
return false; return false;
@ -973,20 +708,15 @@ public class XMLElement {
} }
appendAttributes(writer, newLineAttributes); appendAttributes(writer, newLineAttributes);
boolean useEndTag=false; boolean useEndTag=false;
if(canAppendChildes()){
writer.write(mEnd);
appendChildes(writer, newLineAttributes);
useEndTag=true;
}
boolean hasTextCon=hasTextContent(); boolean hasTextCon=hasTextContent();
if(hasTextCon){ if(hasTextCon){
if(!useEndTag){
writer.write(mEnd); writer.write(mEnd);
}else {
writer.write(XMLUtil.NEW_LINE);
}
appendTextContent(writer); appendTextContent(writer);
useEndTag=true; useEndTag=true;
}else if(canAppendChildes()){
writer.write(mEnd);
appendChildes(writer, newLineAttributes);
useEndTag=true;
} }
if(useEndTag){ if(useEndTag){
if(!hasTextCon){ if(!hasTextCon){
@ -996,19 +726,13 @@ public class XMLElement {
writer.write(mStart); writer.write(mStart);
writer.write(mStartPrefix); writer.write(mStartPrefix);
writer.write(getTagName()); writer.write(getTagName());
writer.write(mEnd);
}else { }else {
writer.write(mEndPrefix); writer.write(mEndPrefix);
writer.write(mEnd);
} }
writer.write(mEnd);
return true; return true;
} }
public String toText(){ @Override
return toText(1, false);
}
public String toText(boolean newLineAttributes){
return toText(1, newLineAttributes);
}
public String toText(int indent, boolean newLineAttributes){ public String toText(int indent, boolean newLineAttributes){
StringWriter writer=new StringWriter(); StringWriter writer=new StringWriter();
setIndent(indent); setIndent(indent);
@ -1020,6 +744,24 @@ public class XMLElement {
} }
return writer.toString(); return writer.toString();
} }
protected List<XMLNode> listSpannable(){
List<XMLNode> results = new ArrayList<>();
for(XMLNode child:getChildNodes()){
if((child instanceof XMLElement) || (child instanceof XMLText)){
results.add(child);
}
}
return results;
}
protected String getSpannableText() {
StringBuilder builder = new StringBuilder();
builder.append(getTagName());
for(XMLAttribute attribute:listAttributes()){
builder.append(' ');
builder.append(attribute.toText(0, false));
}
return builder.toString();
}
@Override @Override
public String toString(){ public String toString(){
StringWriter strWriter=new StringWriter(); StringWriter strWriter=new StringWriter();
@ -1032,23 +774,4 @@ public class XMLElement {
return strWriter.toString(); return strWriter.toString();
} }
public static List<XMLElement> getAllElementsRecursive(XMLElement parent){
List<XMLElement> results=new ArrayList<>();
if(parent==null){
return results;
}
XMLElement[] allChildes=parent.getAllChildes();
if(allChildes==null){
return results;
}
int max=allChildes.length;
for(int i=0;i<max;i++){
XMLElement child=allChildes[i];
results.add(child);
results.addAll(getAllElementsRecursive(child));
}
return results;
}
} }

View File

@ -0,0 +1,58 @@
/*
* 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.xml;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
public abstract class XMLNode {
private final List<XMLNode> mChildNodes = new ArrayList<>();
void addChildNode(XMLNode xmlNode){
if(xmlNode!=null && canAdd(xmlNode)){
mChildNodes.add(xmlNode);
}
}
boolean canAdd(XMLNode xmlNode){
return !mChildNodes.contains(xmlNode);
}
boolean contains(XMLNode xmlNode){
return mChildNodes.contains(xmlNode);
}
void removeChildNode(XMLNode xmlNode){
int i = mChildNodes.indexOf(xmlNode);
while (i>=0){
i = mChildNodes.indexOf(xmlNode);
}
mChildNodes.remove(xmlNode);
}
public List<XMLNode> getChildNodes() {
return mChildNodes;
}
public boolean write(Writer writer) throws IOException {
return write(writer, false);
}
public String toText(){
return toText(1, false);
}
public String toText(boolean newLineAttributes){
return toText(1, newLineAttributes);
}
public abstract boolean write(Writer writer, boolean newLineAttributes) throws IOException;
public abstract String toText(int indent, boolean newLineAttributes);
}

View File

@ -0,0 +1,27 @@
/*
* 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.xml;
public class XMLSpanInfo {
public final String tag;
public final int start;
public int end;
public XMLSpanInfo(String tag, int start, int end){
this.tag=tag;
this.start=start;
this.end=end;
}
}

View File

@ -0,0 +1,139 @@
/*
* 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.xml;
import com.reandroid.xml.parser.XMLSpanParser;
import java.util.*;
public class XMLSpannable implements Comparable<XMLSpannable>{
private XMLElement mElement;
private String mText;
private List<XMLSpanInfo> mSpanInfoList;
private XMLSpannable(XMLElement element){
this.mElement=element;
}
public boolean isValid(){
List<XMLSpanInfo> spanInfoList = getSpanInfoList();
if(spanInfoList.size()==0){
return false;
}
for(XMLSpanInfo spanInfo:spanInfoList){
if(spanInfo.end<spanInfo.start){
return false;
}
}
return true;
}
public String getText(){
if(mText==null){
buildSpanInfo();
}
return mText;
}
public List<XMLSpanInfo> getSpanInfoList(){
if(mSpanInfoList==null){
buildSpanInfo();
}
return mSpanInfoList;
}
private void buildSpanInfo(){
mSpanInfoList=new ArrayList<>();
StringBuilder builder=new StringBuilder();
buildSpanInfo(mElement, builder);
mText=builder.toString();
mElement=null;
}
private void buildSpanInfo(XMLElement element, StringBuilder builder){
XMLSpanInfo info=null;
for(XMLNode node:element.listSpannable()){
if(info!=null){
info.end=builder.length();
info=null;
}
if(node instanceof XMLText){
builder.append(((XMLText)node).getText());
continue;
}
XMLElement child = (XMLElement) node;
info=new XMLSpanInfo(
child.getSpannableText(),
builder.length(), 0);
mSpanInfoList.add(info);
buildSpanInfo(child, builder);
}
if(info!=null){
info.end=builder.length();
}
}
@Override
public int compareTo(XMLSpannable xmlSpannable) {
return getText().compareTo(xmlSpannable.getText());
}
public static XMLSpannable parse(String text){
if(!hasStyle(text)){
return null;
}
try {
XMLSpannable spannable=new XMLSpannable(PARSER.parse(text));
if(spannable.isValid()){
return spannable;
}
} catch (Exception ignored) {
}
return null;
}
public static Set<String> tagList(Collection<XMLSpannable> spannableList){
Set<String> results=new HashSet<>();
for(XMLSpannable xmlSpannable:spannableList){
for(XMLSpanInfo spanInfo: xmlSpannable.getSpanInfoList()){
results.add(spanInfo.tag);
}
}
return results;
}
public static List<String> toTextList(Collection<XMLSpannable> spannableList){
List<String> results=new ArrayList<>(spannableList.size());
for(XMLSpannable xmlSpannable:spannableList){
results.add(xmlSpannable.getText());
}
return results;
}
public static void sort(List<XMLSpannable> spannableList){
Comparator<XMLSpannable> cmp=new Comparator<XMLSpannable>() {
@Override
public int compare(XMLSpannable s1, XMLSpannable s2) {
return s1.compareTo(s2);
}
};
spannableList.sort(cmp);
}
private static boolean hasStyle(String text){
if(text==null){
return false;
}
int i=text.indexOf('<');
if(i<0){
return false;
}
i=text.indexOf('>');
return i>1;
}
private static final XMLSpanParser PARSER=new XMLSpanParser();
}

View File

@ -0,0 +1,57 @@
/*
* 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.xml;
import java.io.IOException;
import java.io.Writer;
public class XMLText extends XMLNode{
private String text;
public XMLText(String text){
this.text=text;
}
public XMLText(){
this(null);
}
public String getText(){
return getText(true);
}
public String getText(boolean unEscape){
if(unEscape){
return XMLUtil.unEscapeXmlChars(text);
}
return text;
}
public void setText(String text){
this.text=XMLUtil.escapeXmlChars(text);
}
@Override
public boolean write(Writer writer, boolean newLineAttributes) throws IOException {
if(!XMLUtil.isEmpty(this.text)){
writer.write(this.text);
return true;
}
return false;
}
@Override
public String toText(int indent, boolean newLineAttributes) {
return getText(false);
}
@Override
public String toString(){
return getText();
}
}

View File

@ -28,13 +28,6 @@ public class XmlHeaderElement extends XMLElement {
initializeStartEnd(); initializeStartEnd();
setDefaultAttr(); setDefaultAttr();
} }
@Override
XMLElement onCloneElement(){
return new XmlHeaderElement(this);
}
@Override
void cloneAllAttributes(XMLElement element){
}
private void copyAll(XmlHeaderElement element){ private void copyAll(XmlHeaderElement element){
if(element==null){ if(element==null){
return; return;

View File

@ -260,10 +260,10 @@ public class XMLDocumentParser {
} }
String text=mCurrentText.toString(); String text=mCurrentText.toString();
mCurrentText=null; mCurrentText=null;
if(XMLUtil.isEmpty(text)){ if(text.trim().length()==0 && !mCurrentElement.hasTextContent()){
return; return;
} }
mCurrentElement.setTextContent(text, true); mCurrentElement.addText(new XMLText(text));
} }
private void onEntityRef(){ private void onEntityRef(){
String name=mParser.getName(); String name=mParser.getName();

View File

@ -0,0 +1,98 @@
/*
* 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.xml.parser;
import com.reandroid.xml.*;
import java.io.IOException;
import java.io.StringReader;
public class XMLSpanParser {
private final XmlPullParser mParser;
private XMLElement mCurrentElement;
public XMLSpanParser(){
this.mParser = new MXParserNonValidating();
}
public XMLElement parse(String text) throws XMLException {
try {
text="<spannable-parser>"+text+"</spannable-parser>";
parseString(text);
} catch (XmlPullParserException|IOException ex) {
throw new XMLException(ex.getMessage());
}
XMLElement element=mCurrentElement;
mCurrentElement=null;
return element;
}
private void parseString(String text) throws XmlPullParserException, IOException {
mCurrentElement=null;
StringReader reader=new StringReader(text);
this.mParser.setInput(reader);
int type;
while ((type=mParser.nextToken()) !=XmlPullParser.END_DOCUMENT){
event(type);
}
}
private void event(int type) {
if (type == XmlPullParser.START_DOCUMENT){
onStartDocument();
}else if (type == XmlPullParser.START_TAG){
onStartTag();
}else if (type == XmlPullParser.END_TAG){
onEndTag();
}else if (type == XmlPullParser.TEXT){
onText();
}
}
private void loadAttributes(){
int max=mParser.getAttributeCount();
for(int i=0; i<max; i++){
onAttribute(i);
}
}
private void onAttribute(int i){
String attrName=mParser.getAttributeName(i);
String attrValue=mParser.getAttributeValue(i);
mCurrentElement.setAttribute(attrName, attrValue);
}
private void onStartTag() {
String name=mParser.getName();
if(mCurrentElement==null){
mCurrentElement=new XMLElement(name);
}else {
mCurrentElement=mCurrentElement.createElement(name);
}
loadAttributes();
}
private void onEndTag() {
XMLElement parent=mCurrentElement.getParent();
if(parent!=null){
mCurrentElement=parent;
}
}
private void onText() {
String text=mParser.getText();
if(text!=null && text.length()>0){
mCurrentElement.addText(new XMLText(text));
}
}
private void onStartDocument() {
this.mCurrentElement=null;
}
}