diff --git a/src/main/java/com/reandroid/lib/apk/xmlencoder/EncodeUtil.java b/src/main/java/com/reandroid/lib/apk/xmlencoder/EncodeUtil.java index 8ebfbbb..7073c34 100644 --- a/src/main/java/com/reandroid/lib/apk/xmlencoder/EncodeUtil.java +++ b/src/main/java/com/reandroid/lib/apk/xmlencoder/EncodeUtil.java @@ -22,6 +22,15 @@ package com.reandroid.lib.apk.xmlencoder; import java.util.regex.Pattern; public class EncodeUtil { + public static void sortStrings(List stringList){ + Comparator cmp=new Comparator() { + @Override + public int compare(String s1, String s2) { + return s1.compareTo(s2); + } + }; + stringList.sort(cmp); + } public static void sortPublicXml(List fileList){ Comparator cmp=new Comparator() { @Override diff --git a/src/main/java/com/reandroid/lib/apk/xmlencoder/ValuesStringPoolBuilder.java b/src/main/java/com/reandroid/lib/apk/xmlencoder/ValuesStringPoolBuilder.java index 328ea98..4402a9f 100644 --- a/src/main/java/com/reandroid/lib/apk/xmlencoder/ValuesStringPoolBuilder.java +++ b/src/main/java/com/reandroid/lib/apk/xmlencoder/ValuesStringPoolBuilder.java @@ -15,23 +15,75 @@ */ 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.item.StyleItem; +import com.reandroid.lib.arsc.item.TableString; import com.reandroid.lib.arsc.pool.TableStringPool; import com.reandroid.xml.XMLDocument; import com.reandroid.xml.XMLElement; +import com.reandroid.xml.XMLSpanInfo; +import com.reandroid.xml.XMLSpannable; import java.io.File; -import java.util.HashSet; -import java.util.Set; +import java.util.*; -public class ValuesStringPoolBuilder { + public class ValuesStringPoolBuilder { private final Set stringList; public ValuesStringPoolBuilder(){ this.stringList=new HashSet<>(); } public void addTo(TableStringPool stringPool){ + if(stringPool.getStringsArray().childesCount()==0){ + buildWithStyles(stringPool); + } stringPool.addStrings(stringList); stringList.clear(); + stringPool.refresh(); + } + private void buildWithStyles(TableStringPool stringPool){ + List spannableList = buildSpannable(); + if(spannableList.size()==0){ + return; + } + + Map stringsMap = stringPool + .insertStrings(XMLSpannable.toTextList(spannableList)); + + List tagList = + new ArrayList<>(XMLSpannable.tagList(spannableList)); + EncodeUtil.sortStrings(tagList); + Map 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 buildSpannable(){ + List results=new ArrayList<>(); + Set 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){ addStringsFile(new File(dir, "strings.xml")); diff --git a/src/main/java/com/reandroid/lib/arsc/pool/BaseStringPool.java b/src/main/java/com/reandroid/lib/arsc/pool/BaseStringPool.java index 03084d9..0d477fc 100755 --- a/src/main/java/com/reandroid/lib/arsc/pool/BaseStringPool.java +++ b/src/main/java/com/reandroid/lib/arsc/pool/BaseStringPool.java @@ -102,9 +102,9 @@ public abstract class BaseStringPool extends BaseChunk imp } List sortedList=new ArrayList<>(stringList); sortedList.sort(this); - insertStrings(sortedList); + insertStringList(sortedList); } - private void insertStrings(List stringList){ + private void insertStringList(List stringList){ StringArray stringsArray = getStringsArray(); int initialSize=stringsArray.childesCount(); stringsArray.ensureSize(initialSize + stringList.size()); @@ -117,6 +117,23 @@ public abstract class BaseStringPool extends BaseChunk imp } refreshUniqueIdMap(); } + public Map insertStrings(List stringList){ + Map results=new HashMap<>(); + StringArray stringsArray = getStringsArray(); + int initialSize=stringsArray.childesCount(); + stringsArray.ensureSize(initialSize + stringList.size()); + int size=stringsArray.childesCount(); + int j=0; + for (int i=initialSize;i mAttributes; - private List mChildes; + private final List mAttributes = new ArrayList<>(); + private final List mChildes = new ArrayList<>(); private List mComments; + private final List mTexts = new ArrayList<>(); private XMLElement mParent; private int mIndent; private Object mTag; private int mResId; private float mIndentScale; - private String mUniqueAttrName; - private List mUniqueNameValues; private String mStart; private String mStartPrefix; private String mEnd; @@ -48,6 +47,19 @@ public class XMLElement { 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(){ int i=mTagName.indexOf(":"); if(i>0){ @@ -68,45 +80,6 @@ public class XMLElement { this.mStartPrefix="/"; 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(){ if(nameSpaceItems!=null){ for(NameSpaceItem nsItem:nameSpaceItems){ @@ -162,82 +135,24 @@ public class XMLElement { } return null; } - public void setStart(String start) { + void setStart(String start) { this.mStart = start; } - public void setEnd(String end) { + void setEnd(String end) { this.mEnd = end; } - public void setStartPrefix(String pfx) { + void setStartPrefix(String pfx) { if(pfx==null){ pfx=""; } this.mStartPrefix = pfx; } - public void setEndPrefix(String pfx) { + void setEndPrefix(String pfx) { if(pfx==null){ 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){ mIndentScale=scale; } @@ -254,82 +169,11 @@ public class XMLElement { public void setResourceId(int 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) { XMLElement baseElement=new XMLElement(tag); addChildNoCheck(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 elements) { if(elements==null){ return; @@ -338,8 +182,14 @@ public class XMLElement { addChild(element); } } - public void addChild(XMLElement baseElement) { - addChildNoCheck(baseElement); + public void addChild(XMLElement child) { + addChildNoCheck(child); + } + private void clearChildElements(){ + mChildes.clear(); + } + private void clearTexts(){ + mTexts.clear(); } public XMLComment getCommentAt(int index){ if(mComments==null || index<0){ @@ -352,7 +202,7 @@ public class XMLElement { } public void hideComments(boolean recursive, boolean hide){ hideComments(hide); - if(recursive && mChildes!=null){ + if(recursive){ for(XMLElement child:mChildes){ child.hideComments(recursive, hide); } @@ -397,75 +247,30 @@ public class XMLElement { mComments.add(commentElement); commentElement.setIndent(getIndent()); commentElement.setParent(this); + super.addChildNode(commentElement); } - public void removeAllChildes(){ - if(mChildes==null){ - return; - } + public void removeChildElements(){ mChildes.clear(); } + public List listAttributes(){ + return mAttributes; + } public int getChildesCount(){ - if(mChildes==null){ - return 0; - } return mChildes.size(); } - public XMLElement getFirstElementByTagName(String tagName){ - if(tagName==null||mChildes==null){ - 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 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 List listChildElements(){ + return mChildes; } public XMLElement getChildAt(int index){ - if(mChildes==null||index<0){ - return null; - } - if(index>=mChildes.size()){ + if(index<0 || index>=mChildes.size()){ return null; } return mChildes.get(index); } public int getAttributeCount(){ - if(mAttributes==null){ - return 0; - } return mAttributes.size(); } public XMLAttribute getAttributeAt(int index){ - if(mAttributes==null||index<0){ - return null; - } if(index>=mAttributes.size()){ return null; } @@ -514,9 +319,6 @@ public class XMLElement { return attr.getValueBool(); } public XMLAttribute getAttribute(String name){ - if(mAttributes==null){ - return null; - } if(XMLUtil.isEmpty(name)){ return null; } @@ -585,42 +387,22 @@ public class XMLElement { if(exist!=null){ return; } - if(mAttributes==null){ - mAttributes=new ArrayList<>(); - } mAttributes.add(attr); } private void addAttributeNoCheck(XMLAttribute attr){ - if(attr==null){ + if(attr==null || attr.isEmpty()){ return; } - if(XMLUtil.isEmpty(attr.getName())){ - return; - } - if(mAttributes==null){ - mAttributes=new ArrayList<>(); - } mAttributes.add(attr); } public void sortChildes(Comparator comparator){ - if(mChildes==null||comparator==null){ + if(comparator==null){ return; } mChildes.sort(comparator); } - public void sortAttributesWithChildes(Comparator comparator){ - if(comparator==null){ - return; - } - sortAttributes(comparator); - if(mChildes!=null){ - for(XMLElement element:mChildes){ - element.sortAttributesWithChildes(comparator); - } - } - } public void sortAttributes(Comparator comparator){ - if(mAttributes==null || comparator==null){ + if(comparator==null){ return; } mAttributes.sort(comparator); @@ -631,16 +413,14 @@ public class XMLElement { void setParent(XMLElement baseElement){ mParent=baseElement; } - private void addChildNoCheck(XMLElement baseElement){ - if(baseElement==null){ + private void addChildNoCheck(XMLElement child){ + if(child==null || child == this){ return; } - if(mChildes==null){ - mChildes=new ArrayList<>(); - } - baseElement.setParent(this); - baseElement.setIndent(getChildIndent()); - mChildes.add(baseElement); + child.setParent(this); + child.setIndent(getChildIndent()); + mChildes.add(child); + super.addChildNode(child); } public int getLevel(){ int rs=0; @@ -652,10 +432,13 @@ public class XMLElement { return rs; } int getIndent(){ + if(hasTextContent()){ + return 0; + } return mIndent; } int getChildIndent(){ - if(mIndent<=0){ + if(mIndent<=0 || hasTextContent()){ return 0; } int rs=mIndent+1; @@ -671,11 +454,9 @@ public class XMLElement { } public void setIndent(int indent){ mIndent=indent; - if(mChildes!=null){ - int chIndent=getChildIndent(); - for(XMLElement be:mChildes){ - be.setIndent(chIndent); - } + int chIndent=getChildIndent(); + for(XMLElement child:mChildes){ + child.setIndent(chIndent); } if(mComments!=null){ 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()0; } - public XMLAttribute setTextContent(String text, boolean escape){ - XMLTextAttribute textAttribute= getTextAttr(); - if(XMLUtil.isEmpty(text)){ - textAttribute.setText(null); - //textAttribute.setText(text); - }else { - if(escape){ - text= XMLUtil.escapeXmlChars(text); - } - textAttribute.setText(text); + public void setTextContent(String text){ + setTextContent(text, true); + } + public void setTextContent(String text, boolean escape){ + clearChildElements(); + clearTexts(); + if(escape){ + text=XMLUtil.escapeXmlChars(text); } - return textAttribute; + appendText(text); } private boolean appendAttributes(Writer writer, boolean newLineAttributes) throws IOException { if(mAttributes==null){ @@ -851,50 +614,24 @@ public class XMLElement { } 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(){ if(mTagName!=null){ return false; } - if(mAttributes!=null && mAttributes.size()>0){ + if(mAttributes.size()>0){ return false; } if(mComments!=null && mComments.size()>0){ return false; } - return getTextContent()==null; - } - private boolean canAppendChildes(){ - if(mChildes==null){ + if(mTexts.size()>0){ return false; } - for(XMLElement be:mChildes){ - if (!be.isEmpty()){ + return true; + } + private boolean canAppendChildes(){ + for(XMLElement child:mChildes){ + if (!child.isEmpty()){ return true; } } @@ -921,22 +658,19 @@ public class XMLElement { return addedOnce; } private boolean appendChildes(Writer writer, boolean newLineAttributes) throws IOException { - if(mChildes==null){ - return false; - } boolean appendPrevious=true; boolean addedOnce=false; - for(XMLElement be:mChildes){ + for(XMLElement child:mChildes){ if(stopWriting(writer)){ break; } - if(be.isEmpty()){ + if(child.isEmpty()){ continue; } if(appendPrevious){ writer.write(XMLUtil.NEW_LINE); } - appendPrevious=be.write(writer, newLineAttributes); + appendPrevious=child.write(writer, newLineAttributes); if(!addedOnce && appendPrevious){ addedOnce=true; } @@ -954,6 +688,7 @@ public class XMLElement { } return false; } + @Override public boolean write(Writer writer, boolean newLineAttributes) throws IOException { if(isEmpty()){ return false; @@ -973,20 +708,15 @@ public class XMLElement { } appendAttributes(writer, newLineAttributes); boolean useEndTag=false; - if(canAppendChildes()){ - writer.write(mEnd); - appendChildes(writer, newLineAttributes); - useEndTag=true; - } boolean hasTextCon=hasTextContent(); if(hasTextCon){ - if(!useEndTag){ - writer.write(mEnd); - }else { - writer.write(XMLUtil.NEW_LINE); - } + writer.write(mEnd); appendTextContent(writer); useEndTag=true; + }else if(canAppendChildes()){ + writer.write(mEnd); + appendChildes(writer, newLineAttributes); + useEndTag=true; } if(useEndTag){ if(!hasTextCon){ @@ -996,19 +726,13 @@ public class XMLElement { writer.write(mStart); writer.write(mStartPrefix); writer.write(getTagName()); - writer.write(mEnd); }else { writer.write(mEndPrefix); - writer.write(mEnd); } + writer.write(mEnd); return true; } - public String toText(){ - return toText(1, false); - } - public String toText(boolean newLineAttributes){ - return toText(1, newLineAttributes); - } + @Override public String toText(int indent, boolean newLineAttributes){ StringWriter writer=new StringWriter(); setIndent(indent); @@ -1020,6 +744,24 @@ public class XMLElement { } return writer.toString(); } + protected List listSpannable(){ + List 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 public String toString(){ StringWriter strWriter=new StringWriter(); @@ -1032,23 +774,4 @@ public class XMLElement { return strWriter.toString(); } - public static List getAllElementsRecursive(XMLElement parent){ - List 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 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 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); +} diff --git a/src/main/java/com/reandroid/xml/XMLSpanInfo.java b/src/main/java/com/reandroid/xml/XMLSpanInfo.java new file mode 100644 index 0000000..17e082b --- /dev/null +++ b/src/main/java/com/reandroid/xml/XMLSpanInfo.java @@ -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; + } +} diff --git a/src/main/java/com/reandroid/xml/XMLSpannable.java b/src/main/java/com/reandroid/xml/XMLSpannable.java new file mode 100644 index 0000000..ae4cc6f --- /dev/null +++ b/src/main/java/com/reandroid/xml/XMLSpannable.java @@ -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{ + private XMLElement mElement; + private String mText; + private List mSpanInfoList; + private XMLSpannable(XMLElement element){ + this.mElement=element; + } + public boolean isValid(){ + List spanInfoList = getSpanInfoList(); + if(spanInfoList.size()==0){ + return false; + } + for(XMLSpanInfo spanInfo:spanInfoList){ + if(spanInfo.end 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 tagList(Collection spannableList){ + Set results=new HashSet<>(); + for(XMLSpannable xmlSpannable:spannableList){ + for(XMLSpanInfo spanInfo: xmlSpannable.getSpanInfoList()){ + results.add(spanInfo.tag); + } + } + return results; + } + public static List toTextList(Collection spannableList){ + List results=new ArrayList<>(spannableList.size()); + for(XMLSpannable xmlSpannable:spannableList){ + results.add(xmlSpannable.getText()); + } + return results; + } + public static void sort(List spannableList){ + Comparator cmp=new Comparator() { + @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(); + +} diff --git a/src/main/java/com/reandroid/xml/XMLText.java b/src/main/java/com/reandroid/xml/XMLText.java new file mode 100644 index 0000000..94fd9db --- /dev/null +++ b/src/main/java/com/reandroid/xml/XMLText.java @@ -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(); + } +} diff --git a/src/main/java/com/reandroid/xml/XmlHeaderElement.java b/src/main/java/com/reandroid/xml/XmlHeaderElement.java index 8a96b1b..bc44564 100755 --- a/src/main/java/com/reandroid/xml/XmlHeaderElement.java +++ b/src/main/java/com/reandroid/xml/XmlHeaderElement.java @@ -28,13 +28,6 @@ public class XmlHeaderElement extends XMLElement { initializeStartEnd(); setDefaultAttr(); } - @Override - XMLElement onCloneElement(){ - return new XmlHeaderElement(this); - } - @Override - void cloneAllAttributes(XMLElement element){ - } private void copyAll(XmlHeaderElement element){ if(element==null){ return; diff --git a/src/main/java/com/reandroid/xml/parser/XMLDocumentParser.java b/src/main/java/com/reandroid/xml/parser/XMLDocumentParser.java index 57ca3f7..df4b061 100755 --- a/src/main/java/com/reandroid/xml/parser/XMLDocumentParser.java +++ b/src/main/java/com/reandroid/xml/parser/XMLDocumentParser.java @@ -260,10 +260,10 @@ public class XMLDocumentParser { } String text=mCurrentText.toString(); mCurrentText=null; - if(XMLUtil.isEmpty(text)){ + if(text.trim().length()==0 && !mCurrentElement.hasTextContent()){ return; } - mCurrentElement.setTextContent(text, true); + mCurrentElement.addText(new XMLText(text)); } private void onEntityRef(){ String name=mParser.getName(); diff --git a/src/main/java/com/reandroid/xml/parser/XMLSpanParser.java b/src/main/java/com/reandroid/xml/parser/XMLSpanParser.java new file mode 100644 index 0000000..8be1782 --- /dev/null +++ b/src/main/java/com/reandroid/xml/parser/XMLSpanParser.java @@ -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=""+text+""; + 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; i0){ + mCurrentElement.addText(new XMLText(text)); + } + } + private void onStartDocument() { + this.mCurrentElement=null; + } +}