diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..43e91eb --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,203 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + diff --git a/NOTICE.txt b/NOTICE.txt new file mode 100644 index 0000000..24f2859 --- /dev/null +++ b/NOTICE.txt @@ -0,0 +1,4 @@ +Xpatch - Tool to work with android apk file, it can insert xposed loader into the apk file, and repackage the apk, then +the repacked apk can load xposed module when started. + +Copyright (c) 2019 Wind diff --git a/axmlprinter/build.gradle b/axmlprinter/build.gradle new file mode 100644 index 0000000..f3a60b2 --- /dev/null +++ b/axmlprinter/build.gradle @@ -0,0 +1,6 @@ +description = 'android manifest content parser' +apply plugin: 'java-library' + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) +} \ No newline at end of file diff --git a/axmlprinter/src/main/java/wind/android/content/res/AXmlResourceParser.java b/axmlprinter/src/main/java/wind/android/content/res/AXmlResourceParser.java new file mode 100644 index 0000000..bc1e9c3 --- /dev/null +++ b/axmlprinter/src/main/java/wind/android/content/res/AXmlResourceParser.java @@ -0,0 +1,932 @@ +/* + * Copyright 2008 Android4ME + * + * 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 wind.android.content.res; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import wind.v1.XmlPullParserException; + +import wind.android.content.res.ChunkUtil; +import wind.android.content.res.IntReader; +import wind.android.content.res.StringBlock; +import wind.android.content.res.XmlResourceParser; +import wind.android.util.TypedValue; + +/** + * @author Dmitry Skiba + * + * Binary xml files parser. + * + * Parser has only two states: + * (1) Operational state, which parser obtains after first successful call + * to next() and retains until open(), close(), or failed call to next(). + * (2) Closed state, which parser obtains after open(), close(), or failed + * call to next(). In this state methods return invalid values or throw exceptions. + * + * TODO: + * * check all methods in closed state + * + */ +public class AXmlResourceParser implements XmlResourceParser { + + public AXmlResourceParser() { + resetEventInfo(); + } + + public void open(InputStream stream) { + close(); + if (stream!=null) { + m_reader=new wind.android.content.res.IntReader(stream,false); + } + } + + public void close() { + if (!m_operational) { + return; + } + m_operational=false; + m_reader.close(); + m_reader=null; + m_strings=null; + m_resourceIDs=null; + m_namespaces.reset(); + resetEventInfo(); + } + + /////////////////////////////////// iteration + + public int next() throws XmlPullParserException,IOException { + if (m_reader==null) { + throw new XmlPullParserException("Parser is not opened.",this,null); + } + try { + doNext(); + return m_event; + } + catch (IOException e) { + close(); + throw e; + } + } + + public int nextToken() throws XmlPullParserException,IOException { + return next(); + } + + public int nextTag() throws XmlPullParserException,IOException { + int eventType=next(); + if (eventType==TEXT && isWhitespace()) { + eventType=next(); + } + if (eventType!=START_TAG && eventType!=END_TAG) { + throw new XmlPullParserException("Expected start or end tag.",this,null); + } + return eventType; + } + + public String nextText() throws XmlPullParserException,IOException { + if(getEventType()!=START_TAG) { + throw new XmlPullParserException("Parser must be on START_TAG to read next text.",this,null); + } + int eventType=next(); + if (eventType==TEXT) { + String result=getText(); + eventType=next(); + if (eventType!=END_TAG) { + throw new XmlPullParserException("Event TEXT must be immediately followed by END_TAG.",this,null); + } + return result; + } else if (eventType==END_TAG) { + return ""; + } else { + throw new XmlPullParserException("Parser must be on START_TAG or TEXT to read text.",this,null); + } + } + + public void require(int type,String namespace,String name) throws XmlPullParserException,IOException { + if (type!=getEventType() || + (namespace!=null && !namespace.equals(getNamespace())) || + (name!=null && !name.equals(getName()))) + { + throw new XmlPullParserException(TYPES[type]+" is expected.",this,null); + } + } + + public int getDepth() { + return m_namespaces.getDepth()-1; + } + + public int getEventType() throws XmlPullParserException { + return m_event; + } + + public int getLineNumber() { + return m_lineNumber; + } + + public String getName() { + if (m_name==-1 || (m_event!=START_TAG && m_event!=END_TAG)) { + return null; + } + return m_strings.getString(m_name); + } + + public String getText() { + if (m_name==-1 || m_event!=TEXT) { + return null; + } + return m_strings.getString(m_name); + } + + public char[] getTextCharacters(int[] holderForStartAndLength) { + String text=getText(); + if (text==null) { + return null; + } + holderForStartAndLength[0]=0; + holderForStartAndLength[1]=text.length(); + char[] chars=new char[text.length()]; + text.getChars(0,text.length(),chars,0); + return chars; + } + + public String getNamespace() { + return m_strings.getString(m_namespaceUri); + } + + public String getPrefix() { + int prefix=m_namespaces.findPrefix(m_namespaceUri); + return m_strings.getString(prefix); + } + + public String getPositionDescription() { + return "XML line #"+getLineNumber(); + } + + public int getNamespaceCount(int depth) throws XmlPullParserException { + return m_namespaces.getAccumulatedCount(depth); + } + + public String getNamespacePrefix(int pos) throws XmlPullParserException { + int prefix=m_namespaces.getPrefix(pos); + return m_strings.getString(prefix); + } + + public String getNamespaceUri(int pos) throws XmlPullParserException { + int uri=m_namespaces.getUri(pos); + return m_strings.getString(uri); + } + + /////////////////////////////////// attributes + + public String getClassAttribute() { + if (m_classAttribute==-1) { + return null; + } + int offset=getAttributeOffset(m_classAttribute); + int value=m_attributes[offset+ATTRIBUTE_IX_VALUE_STRING]; + return m_strings.getString(value); + } + + public String getIdAttribute() { + if (m_idAttribute==-1) { + return null; + } + int offset=getAttributeOffset(m_idAttribute); + int value=m_attributes[offset+ATTRIBUTE_IX_VALUE_STRING]; + return m_strings.getString(value); + } + + public int getIdAttributeResourceValue(int defaultValue) { + if (m_idAttribute==-1) { + return defaultValue; + } + int offset=getAttributeOffset(m_idAttribute); + int valueType=m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE]; + if (valueType!=TypedValue.TYPE_REFERENCE) { + return defaultValue; + } + return m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA]; + } + + public int getStyleAttribute() { + if (m_styleAttribute==-1) { + return 0; + } + int offset=getAttributeOffset(m_styleAttribute); + return m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA]; + } + + public int getAttributeCount() { + if (m_event!=START_TAG) { + return -1; + } + return m_attributes.length/ATTRIBUTE_LENGHT; + } + + public String getAttributeNamespace(int index) { + int offset=getAttributeOffset(index); + int namespace=m_attributes[offset+ATTRIBUTE_IX_NAMESPACE_URI]; + if (namespace==-1) { + return ""; + } + return m_strings.getString(namespace); + } + + public String getAttributePrefix(int index) { + int offset=getAttributeOffset(index); + int uri=m_attributes[offset+ATTRIBUTE_IX_NAMESPACE_URI]; + int prefix=m_namespaces.findPrefix(uri); + if (prefix==-1) { + return ""; + } + return m_strings.getString(prefix); + } + + public String getAttributeName(int index) { + int offset=getAttributeOffset(index); + int name=m_attributes[offset+ATTRIBUTE_IX_NAME]; + if (name==-1) { + return ""; + } + return m_strings.getString(name); + } + + public int getAttributeNameResource(int index) { + int offset=getAttributeOffset(index); + int name=m_attributes[offset+ATTRIBUTE_IX_NAME]; + if (m_resourceIDs==null || + name<0 || name>=m_resourceIDs.length) + { + return 0; + } + return m_resourceIDs[name]; + } + + public int getAttributeValueType(int index) { + int offset=getAttributeOffset(index); + return m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE]; + } + + public int getAttributeValueData(int index) { + int offset=getAttributeOffset(index); + return m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA]; + } + + public String getAttributeValue(int index) { + int offset=getAttributeOffset(index); + int valueType=m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE]; + if (valueType==TypedValue.TYPE_STRING) { + int valueString=m_attributes[offset+ATTRIBUTE_IX_VALUE_STRING]; + return m_strings.getString(valueString); + } + int valueData=m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA]; + return "";//TypedValue.coerceToString(valueType,valueData); + } + + public boolean getAttributeBooleanValue(int index,boolean defaultValue) { + return getAttributeIntValue(index,defaultValue?1:0)!=0; + } + + public float getAttributeFloatValue(int index,float defaultValue) { + int offset=getAttributeOffset(index); + int valueType=m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE]; + if (valueType==TypedValue.TYPE_FLOAT) { + int valueData=m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA]; + return Float.intBitsToFloat(valueData); + } + return defaultValue; + } + + public int getAttributeIntValue(int index,int defaultValue) { + int offset=getAttributeOffset(index); + int valueType=m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE]; + if (valueType>=TypedValue.TYPE_FIRST_INT && + valueType<=TypedValue.TYPE_LAST_INT) + { + return m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA]; + } + return defaultValue; + } + + public int getAttributeUnsignedIntValue(int index,int defaultValue) { + return getAttributeIntValue(index,defaultValue); + } + + public int getAttributeResourceValue(int index,int defaultValue) { + int offset=getAttributeOffset(index); + int valueType=m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE]; + if (valueType==TypedValue.TYPE_REFERENCE) { + return m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA]; + } + return defaultValue; + } + + public String getAttributeValue(String namespace,String attribute) { + int index=findAttribute(namespace,attribute); + if (index==-1) { + return null; + } + return getAttributeValue(index); + } + + public boolean getAttributeBooleanValue(String namespace,String attribute,boolean defaultValue) { + int index=findAttribute(namespace,attribute); + if (index==-1) { + return defaultValue; + } + return getAttributeBooleanValue(index,defaultValue); + } + + public float getAttributeFloatValue(String namespace,String attribute,float defaultValue) { + int index=findAttribute(namespace,attribute); + if (index==-1) { + return defaultValue; + } + return getAttributeFloatValue(index,defaultValue); + } + + public int getAttributeIntValue(String namespace,String attribute,int defaultValue) { + int index=findAttribute(namespace,attribute); + if (index==-1) { + return defaultValue; + } + return getAttributeIntValue(index,defaultValue); + } + + public int getAttributeUnsignedIntValue(String namespace,String attribute,int defaultValue) { + int index=findAttribute(namespace,attribute); + if (index==-1) { + return defaultValue; + } + return getAttributeUnsignedIntValue(index,defaultValue); + } + + public int getAttributeResourceValue(String namespace,String attribute,int defaultValue) { + int index=findAttribute(namespace,attribute); + if (index==-1) { + return defaultValue; + } + return getAttributeResourceValue(index,defaultValue); + } + + public int getAttributeListValue(int index,String[] options,int defaultValue) { + // TODO implement + return 0; + } + + public int getAttributeListValue(String namespace,String attribute,String[] options,int defaultValue) { + // TODO implement + return 0; + } + + public String getAttributeType(int index) { + return "CDATA"; + } + + public boolean isAttributeDefault(int index) { + return false; + } + + /////////////////////////////////// dummies + + public void setInput(InputStream stream,String inputEncoding) throws XmlPullParserException { + throw new XmlPullParserException(E_NOT_SUPPORTED); + } + public void setInput(Reader reader) throws XmlPullParserException { + throw new XmlPullParserException(E_NOT_SUPPORTED); + } + + public String getInputEncoding() { + return null; + } + + public int getColumnNumber() { + return -1; + } + + public boolean isEmptyElementTag() throws XmlPullParserException { + return false; + } + + public boolean isWhitespace() throws XmlPullParserException { + return false; + } + + public void defineEntityReplacementText(String entityName,String replacementText) throws XmlPullParserException { + throw new XmlPullParserException(E_NOT_SUPPORTED); + } + + public String getNamespace(String prefix) { + throw new RuntimeException(E_NOT_SUPPORTED); + } + + public Object getProperty(String name) { + return null; + } + public void setProperty(String name,Object value) throws XmlPullParserException { + throw new XmlPullParserException(E_NOT_SUPPORTED); + } + + public boolean getFeature(String feature) { + return false; + } + public void setFeature(String name,boolean value) throws XmlPullParserException { + throw new XmlPullParserException(E_NOT_SUPPORTED); + } + + ///////////////////////////////////////////// implementation + + /** + * Namespace stack, holds prefix+uri pairs, as well as + * depth information. + * All information is stored in one int[] array. + * Array consists of depth frames: + * Data=DepthFrame*; + * DepthFrame=Count+[Prefix+Uri]*+Count; + * Count='count of Prefix+Uri pairs'; + * Yes, count is stored twice, to enable bottom-up traversal. + * increaseDepth adds depth frame, decreaseDepth removes it. + * push/pop operations operate only in current depth frame. + * decreaseDepth removes any remaining (not pop'ed) namespace pairs. + * findXXX methods search all depth frames starting + * from the last namespace pair of current depth frame. + * All functions that operate with int, use -1 as 'invalid value'. + * + * !! functions expect 'prefix'+'uri' pairs, not 'uri'+'prefix' !! + * + */ + private static final class NamespaceStack { + public NamespaceStack() { + m_data=new int[32]; + } + + public final void reset() { + m_dataLength=0; + m_count=0; + m_depth=0; + } + + public final int getTotalCount() { + return m_count; + } + + public final int getCurrentCount() { + if (m_dataLength==0) { + return 0; + } + int offset=m_dataLength-1; + return m_data[offset]; + } + + public final int getAccumulatedCount(int depth) { + if (m_dataLength==0 || depth<0) { + return 0; + } + if (depth>m_depth) { + depth=m_depth; + } + int accumulatedCount=0; + int offset=0; + for (;depth!=0;--depth) { + int count=m_data[offset]; + accumulatedCount+=count; + offset+=(2+count*2); + } + return accumulatedCount; + } + + public final void push(int prefix,int uri) { + if (m_depth==0) { + increaseDepth(); + } + ensureDataCapacity(2); + int offset=m_dataLength-1; + int count=m_data[offset]; + m_data[offset-1-count*2]=count+1; + m_data[offset]=prefix; + m_data[offset+1]=uri; + m_data[offset+2]=count+1; + m_dataLength+=2; + m_count+=1; + } + + public final boolean pop(int prefix,int uri) { + if (m_dataLength==0) { + return false; + } + int offset=m_dataLength-1; + int count=m_data[offset]; + for (int i=0,o=offset-2;i!=count;++i,o-=2) { + if (m_data[o]!=prefix || m_data[o+1]!=uri) { + continue; + } + count-=1; + if (i==0) { + m_data[o]=count; + o-=(1+count*2); + m_data[o]=count; + } else { + m_data[offset]=count; + offset-=(1+2+count*2); + m_data[offset]=count; + System.arraycopy( + m_data,o+2, + m_data,o, + m_dataLength-o); + } + m_dataLength-=2; + m_count-=1; + return true; + } + return false; + } + + public final boolean pop() { + if (m_dataLength==0) { + return false; + } + int offset=m_dataLength-1; + int count=m_data[offset]; + if (count==0) { + return false; + } + count-=1; + offset-=2; + m_data[offset]=count; + offset-=(1+count*2); + m_data[offset]=count; + m_dataLength-=2; + m_count-=1; + return true; + } + + public final int getPrefix(int index) { + return get(index,true); + } + + public final int getUri(int index) { + return get(index,false); + } + + public final int findPrefix(int uri) { + return find(uri,false); + } + + public final int findUri(int prefix) { + return find(prefix,true); + } + + public final int getDepth() { + return m_depth; + } + + public final void increaseDepth() { + ensureDataCapacity(2); + int offset=m_dataLength; + m_data[offset]=0; + m_data[offset+1]=0; + m_dataLength+=2; + m_depth+=1; + } + public final void decreaseDepth() { + if (m_dataLength==0) { + return; + } + int offset=m_dataLength-1; + int count=m_data[offset]; + if ((offset-1-count*2)==0) { + return; + } + m_dataLength-=2+count*2; + m_count-=count; + m_depth-=1; + } + + private void ensureDataCapacity(int capacity) { + int available=(m_data.length-m_dataLength); + if (available>capacity) { + return; + } + int newLength=(m_data.length+available)*2; + int[] newData=new int[newLength]; + System.arraycopy(m_data,0,newData,0,m_dataLength); + m_data=newData; + } + + private final int find(int prefixOrUri,boolean prefix) { + if (m_dataLength==0) { + return -1; + } + int offset=m_dataLength-1; + for (int i=m_depth;i!=0;--i) { + int count=m_data[offset]; + offset-=2; + for (;count!=0;--count) { + if (prefix) { + if (m_data[offset]==prefixOrUri) { + return m_data[offset+1]; + } + } else { + if (m_data[offset+1]==prefixOrUri) { + return m_data[offset]; + } + } + offset-=2; + } + } + return -1; + } + + private final int get(int index,boolean prefix) { + if (m_dataLength==0 || index<0) { + return -1; + } + int offset=0; + for (int i=m_depth;i!=0;--i) { + int count=m_data[offset]; + if (index>=count) { + index-=count; + offset+=(2+count*2); + continue; + } + offset+=(1+index*2); + if (!prefix) { + offset+=1; + } + return m_data[offset]; + } + return -1; + } + + private int[] m_data; + private int m_dataLength; + private int m_count; + private int m_depth; + } + + /////////////////////////////////// package-visible + +// final void fetchAttributes(int[] styleableIDs,TypedArray result) { +// result.resetIndices(); +// if (m_attributes==null || m_resourceIDs==null) { +// return; +// } +// boolean needStrings=false; +// for (int i=0,e=styleableIDs.length;i!=e;++i) { +// int id=styleableIDs[i]; +// for (int o=0;o!=m_attributes.length;o+=ATTRIBUTE_LENGHT) { +// int name=m_attributes[o+ATTRIBUTE_IX_NAME]; +// if (name>=m_resourceIDs.length || +// m_resourceIDs[name]!=id) +// { +// continue; +// } +// int valueType=m_attributes[o+ATTRIBUTE_IX_VALUE_TYPE]; +// int valueData; +// int assetCookie; +// if (valueType==TypedValue.TYPE_STRING) { +// valueData=m_attributes[o+ATTRIBUTE_IX_VALUE_STRING]; +// assetCookie=-1; +// needStrings=true; +// } else { +// valueData=m_attributes[o+ATTRIBUTE_IX_VALUE_DATA]; +// assetCookie=0; +// } +// result.addValue(i,valueType,valueData,assetCookie,id,0); +// } +// } +// if (needStrings) { +// result.setStrings(m_strings); +// } +// } + + final wind.android.content.res.StringBlock getStrings() { + return m_strings; + } + + /////////////////////////////////// + + private final int getAttributeOffset(int index) { + if (m_event!=START_TAG) { + throw new IndexOutOfBoundsException("Current event is not START_TAG."); + } + int offset=index*5; + if (offset>=m_attributes.length) { + throw new IndexOutOfBoundsException("Invalid attribute index ("+index+")."); + } + return offset; + } + + private final int findAttribute(String namespace,String attribute) { + if (m_strings==null || attribute==null) { + return -1; + } + int name=m_strings.find(attribute); + if (name==-1) { + return -1; + } + int uri=(namespace!=null)? + m_strings.find(namespace): + -1; + for (int o=0;o!=m_attributes.length;++o) { + if (name==m_attributes[o+ATTRIBUTE_IX_NAME] && + (uri==-1 || uri==m_attributes[o+ATTRIBUTE_IX_NAMESPACE_URI])) + { + return o/ATTRIBUTE_LENGHT; + } + } + return -1; + } + + private final void resetEventInfo() { + m_event=-1; + m_lineNumber=-1; + m_name=-1; + m_namespaceUri=-1; + m_attributes=null; + m_idAttribute=-1; + m_classAttribute=-1; + m_styleAttribute=-1; + } + + private final void doNext() throws IOException { + // Delayed initialization. + if (m_strings==null) { + wind.android.content.res.ChunkUtil.readCheckType(m_reader,CHUNK_AXML_FILE); + /*chunkSize*/m_reader.skipInt(); + m_strings= wind.android.content.res.StringBlock.read(m_reader); + m_namespaces.increaseDepth(); + m_operational=true; + } + + if (m_event==END_DOCUMENT) { + return; + } + + int event=m_event; + resetEventInfo(); + + while (true) { + if (m_decreaseDepth) { + m_decreaseDepth=false; + m_namespaces.decreaseDepth(); + } + + // Fake END_DOCUMENT event. + if (event==END_TAG && + m_namespaces.getDepth()==1 && + m_namespaces.getCurrentCount()==0) + { + m_event=END_DOCUMENT; + break; + } + + int chunkType; + if (event==START_DOCUMENT) { + // Fake event, see CHUNK_XML_START_TAG handler. + chunkType=CHUNK_XML_START_TAG; + } else { + chunkType=m_reader.readInt(); + } + + if (chunkType==CHUNK_RESOURCEIDS) { + int chunkSize=m_reader.readInt(); + if (chunkSize<8 || (chunkSize%4)!=0) { + throw new IOException("Invalid resource ids size ("+chunkSize+")."); + } + m_resourceIDs=m_reader.readIntArray(chunkSize/4-2); + continue; + } + + if (chunkTypeCHUNK_XML_LAST) { + throw new IOException("Invalid chunk type ("+chunkType+")."); + } + + // Fake START_DOCUMENT event. + if (chunkType==CHUNK_XML_START_TAG && event==-1) { + m_event=START_DOCUMENT; + break; + } + + // Common header. + /*chunkSize*/m_reader.skipInt(); + int lineNumber=m_reader.readInt(); + /*0xFFFFFFFF*/m_reader.skipInt(); + + if (chunkType==CHUNK_XML_START_NAMESPACE || + chunkType==CHUNK_XML_END_NAMESPACE) + { + if (chunkType==CHUNK_XML_START_NAMESPACE) { + int prefix=m_reader.readInt(); + int uri=m_reader.readInt(); + m_namespaces.push(prefix,uri); + } else { + /*prefix*/m_reader.skipInt(); + /*uri*/m_reader.skipInt(); + m_namespaces.pop(); + } + continue; + } + + m_lineNumber=lineNumber; + + if (chunkType==CHUNK_XML_START_TAG) { + m_namespaceUri=m_reader.readInt(); + m_name=m_reader.readInt(); + /*flags?*/m_reader.skipInt(); + int attributeCount=m_reader.readInt(); + m_idAttribute=(attributeCount>>>16)-1; + attributeCount&=0xFFFF; + m_classAttribute=m_reader.readInt(); + m_styleAttribute=(m_classAttribute>>>16)-1; + m_classAttribute=(m_classAttribute & 0xFFFF)-1; + m_attributes=m_reader.readIntArray(attributeCount*ATTRIBUTE_LENGHT); + for (int i=ATTRIBUTE_IX_VALUE_TYPE;i>>24); + i+=ATTRIBUTE_LENGHT; + } + m_namespaces.increaseDepth(); + m_event=START_TAG; + break; + } + + if (chunkType==CHUNK_XML_END_TAG) { + m_namespaceUri=m_reader.readInt(); + m_name=m_reader.readInt(); + m_event=END_TAG; + m_decreaseDepth=true; + break; + } + + if (chunkType==CHUNK_XML_TEXT) { + m_name=m_reader.readInt(); + /*?*/m_reader.skipInt(); + /*?*/m_reader.skipInt(); + m_event=TEXT; + break; + } + } + } + + /////////////////////////////////// data + + /* + * All values are essentially indices, e.g. m_name is + * an index of name in m_strings. + */ + + private IntReader m_reader; + private boolean m_operational=false; + + private StringBlock m_strings; + private int[] m_resourceIDs; + private NamespaceStack m_namespaces=new NamespaceStack(); + + private boolean m_decreaseDepth; + + private int m_event; + private int m_lineNumber; + private int m_name; + private int m_namespaceUri; + private int[] m_attributes; + private int m_idAttribute; + private int m_classAttribute; + private int m_styleAttribute; + + private static final String + E_NOT_SUPPORTED ="Method is not supported."; + + private static final int + ATTRIBUTE_IX_NAMESPACE_URI =0, + ATTRIBUTE_IX_NAME =1, + ATTRIBUTE_IX_VALUE_STRING =2, + ATTRIBUTE_IX_VALUE_TYPE =3, + ATTRIBUTE_IX_VALUE_DATA =4, + ATTRIBUTE_LENGHT =5; + + private static final int + CHUNK_AXML_FILE =0x00080003, + CHUNK_RESOURCEIDS =0x00080180, + CHUNK_XML_FIRST =0x00100100, + CHUNK_XML_START_NAMESPACE =0x00100100, + CHUNK_XML_END_NAMESPACE =0x00100101, + CHUNK_XML_START_TAG =0x00100102, + CHUNK_XML_END_TAG =0x00100103, + CHUNK_XML_TEXT =0x00100104, + CHUNK_XML_LAST =0x00100104; +} diff --git a/axmlprinter/src/main/java/wind/android/content/res/ChunkUtil.java b/axmlprinter/src/main/java/wind/android/content/res/ChunkUtil.java new file mode 100644 index 0000000..199ebd5 --- /dev/null +++ b/axmlprinter/src/main/java/wind/android/content/res/ChunkUtil.java @@ -0,0 +1,36 @@ +/* + * Copyright 2008 Android4ME + * + * 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 wind.android.content.res; + +import wind.android.content.res.IntReader; + +import java.io.IOException; + +/** + * @author Dmitry Skiba + * + */ +class ChunkUtil { + + public static final void readCheckType(IntReader reader, int expectedType) throws IOException { + int type=reader.readInt(); + if (type!=expectedType) { + throw new IOException( + "Expected chunk of type 0x"+Integer.toHexString(expectedType)+ + ", read 0x"+Integer.toHexString(type)+"."); + } + } +} diff --git a/axmlprinter/src/main/java/wind/android/content/res/IntReader.java b/axmlprinter/src/main/java/wind/android/content/res/IntReader.java new file mode 100644 index 0000000..0a006d1 --- /dev/null +++ b/axmlprinter/src/main/java/wind/android/content/res/IntReader.java @@ -0,0 +1,156 @@ +/* + * Copyright 2008 Android4ME + * + * 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 wind.android.content.res; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; + +/** + * @author Dmitry Skiba + * + * Simple helper class that allows reading of integers. + * + * TODO: + * * implement buffering + * + */ +public final class IntReader { + + public IntReader() { + } + public IntReader(InputStream stream,boolean bigEndian) { + reset(stream,bigEndian); + } + + public final void reset(InputStream stream,boolean bigEndian) { + m_stream=stream; + m_bigEndian=bigEndian; + m_position=0; + } + + public final void close() { + if (m_stream==null) { + return; + } + try { + m_stream.close(); + } + catch (IOException e) { + } + reset(null,false); + } + + public final InputStream getStream() { + return m_stream; + } + + public final boolean isBigEndian() { + return m_bigEndian; + } + public final void setBigEndian(boolean bigEndian) { + m_bigEndian=bigEndian; + } + + public final int readByte() throws IOException { + return readInt(1); + } + public final int readShort() throws IOException { + return readInt(2); + } + public final int readInt() throws IOException { + return readInt(4); + } + + public final int readInt(int length) throws IOException { + if (length<0 || length>4) { + throw new IllegalArgumentException(); + } + int result=0; + if (m_bigEndian) { + for (int i=(length-1)*8;i>=0;i-=8) { + int b=m_stream.read(); + if (b==-1) { + throw new EOFException(); + } + m_position+=1; + result|=(b<0;length-=1) { + array[offset++]=readInt(); + } + } + + public final byte[] readByteArray(int length) throws IOException { + byte[] array=new byte[length]; + int read=m_stream.read(array); + m_position+=read; + if (read!=length) { + throw new EOFException(); + } + return array; + } + + public final void skip(int bytes) throws IOException { + if (bytes<=0) { + return; + } + long skipped=m_stream.skip(bytes); + m_position+=skipped; + if (skipped!=bytes) { + throw new EOFException(); + } + } + + public final void skipInt() throws IOException { + skip(4); + } + + public final int available() throws IOException { + return m_stream.available(); + } + + public final int getPosition() { + return m_position; + } + + /////////////////////////////////// data + + private InputStream m_stream; + private boolean m_bigEndian; + private int m_position; +} diff --git a/axmlprinter/src/main/java/wind/android/content/res/StringBlock.java b/axmlprinter/src/main/java/wind/android/content/res/StringBlock.java new file mode 100644 index 0000000..dc6dee4 --- /dev/null +++ b/axmlprinter/src/main/java/wind/android/content/res/StringBlock.java @@ -0,0 +1,248 @@ +/* + * Copyright 2008 Android4ME + * + * 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 wind.android.content.res; + +import wind.android.content.res.ChunkUtil; +import wind.android.content.res.IntReader; + +import java.io.IOException; + +/** + * @author Dmitry Skiba + * + * Block of strings, used in binary xml and arsc. + * + * TODO: + * - implement get() + * + */ +public class StringBlock { + + /** + * Reads whole (including chunk type) string block from stream. + * Stream must be at the chunk type. + */ + public static StringBlock read(IntReader reader) throws IOException { + wind.android.content.res.ChunkUtil.readCheckType(reader,CHUNK_TYPE); + int chunkSize=reader.readInt(); + int stringCount=reader.readInt(); + int styleOffsetCount=reader.readInt(); + /*?*/reader.readInt(); + int stringsOffset=reader.readInt(); + int stylesOffset=reader.readInt(); + + StringBlock block=new StringBlock(); + block.m_stringOffsets=reader.readIntArray(stringCount); + if (styleOffsetCount!=0) { + block.m_styleOffsets=reader.readIntArray(styleOffsetCount); + } + { + int size=((stylesOffset==0)?chunkSize:stylesOffset)-stringsOffset; + if ((size%4)!=0) { + throw new IOException("String data size is not multiple of 4 ("+size+")."); + } + block.m_strings=reader.readIntArray(size/4); + } + if (stylesOffset!=0) { + int size=(chunkSize-stylesOffset); + if ((size%4)!=0) { + throw new IOException("Style data size is not multiple of 4 ("+size+")."); + } + block.m_styles=reader.readIntArray(size/4); + } + + return block; + } + + /** + * Returns number of strings in block. + */ + public int getCount() { + return m_stringOffsets!=null? + m_stringOffsets.length: + 0; + } + + /** + * Returns raw string (without any styling information) at specified index. + */ + public String getString(int index) { + if (index<0 || + m_stringOffsets==null || + index>=m_stringOffsets.length) + { + return null; + } + int offset=m_stringOffsets[index]; + int length=getShort(m_strings,offset); + StringBuilder result=new StringBuilder(length); + for (;length!=0;length-=1) { + offset+=2; + result.append((char)getShort(m_strings,offset)); + } + return result.toString(); + } + + /** + * Not yet implemented. + * + * Returns string with style information (if any). + */ + public CharSequence get(int index) { + return getString(index); + } + + /** + * Returns string with style tags (html-like). + */ + public String getHTML(int index) { + String raw=getString(index); + if (raw==null) { + return raw; + } + int[] style=getStyle(index); + if (style==null) { + return raw; + } + StringBuilder html=new StringBuilder(raw.length()+32); + int offset=0; + while (true) { + int i=-1; + for (int j=0;j!=style.length;j+=3) { + if (style[j+1]==-1) { + continue; + } + if (i==-1 || style[i+1]>style[j+1]) { + i=j; + } + } + int start=((i!=-1)?style[i+1]:raw.length()); + for (int j=0;j!=style.length;j+=3) { + int end=style[j+2]; + if (end==-1 || end>=start) { + continue; + } + if (offset<=end) { + html.append(raw,offset,end+1); + offset=end+1; + } + style[j+2]=-1; + html.append('<'); + html.append('/'); + html.append(getString(style[j])); + html.append('>'); + } + if (offset'); + style[i+1]=-1; + } + return html.toString(); + } + + /** + * Finds index of the string. + * Returns -1 if the string was not found. + */ + public int find(String string) { + if (string==null) { + return -1; + } + for (int i=0;i!=m_stringOffsets.length;++i) { + int offset=m_stringOffsets[i]; + int length=getShort(m_strings,offset); + if (length!=string.length()) { + continue; + } + int j=0; + for (;j!=length;++j) { + offset+=2; + if (string.charAt(j)!=getShort(m_strings,offset)) { + break; + } + } + if (j==length) { + return i; + } + } + return -1; + } + + ///////////////////////////////////////////// implementation + + private StringBlock() { + } + + /** + * Returns style information - array of int triplets, + * where in each triplet: + * * first int is index of tag name ('b','i', etc.) + * * second int is tag start index in string + * * third int is tag end index in string + */ + private int[] getStyle(int index) { + if (m_styleOffsets==null || m_styles==null || + index>=m_styleOffsets.length) + { + return null; + } + int offset=m_styleOffsets[index]/4; + int style[]; + { + int count=0; + for (int i=offset;i>> 16); + } + } + + private int[] m_stringOffsets; + private int[] m_strings; + private int[] m_styleOffsets; + private int[] m_styles; + + private static final int CHUNK_TYPE=0x001C0001; +} diff --git a/axmlprinter/src/main/java/wind/android/content/res/XmlResourceParser.java b/axmlprinter/src/main/java/wind/android/content/res/XmlResourceParser.java new file mode 100644 index 0000000..1efa2f6 --- /dev/null +++ b/axmlprinter/src/main/java/wind/android/content/res/XmlResourceParser.java @@ -0,0 +1,27 @@ +/* + * Copyright 2008 Android4ME + * + * 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 wind.android.content.res; + +import wind.v1.XmlPullParser; +import wind.android.util.AttributeSet; + +/** + * @author Dmitry Skiba + * + */ +public interface XmlResourceParser extends XmlPullParser, AttributeSet { + void close(); +} diff --git a/axmlprinter/src/main/java/wind/android/util/AttributeSet.java b/axmlprinter/src/main/java/wind/android/util/AttributeSet.java new file mode 100644 index 0000000..9d2d1dc --- /dev/null +++ b/axmlprinter/src/main/java/wind/android/util/AttributeSet.java @@ -0,0 +1,49 @@ +/* + * Copyright 2008 Android4ME + * + * 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 wind.android.util; + +/** + * @author Dmitry Skiba + * + */ +public interface AttributeSet { + int getAttributeCount(); + String getAttributeName(int index); + String getAttributeValue(int index); + String getPositionDescription(); + int getAttributeNameResource(int index); + int getAttributeListValue(int index, String options[], int defaultValue); + boolean getAttributeBooleanValue(int index, boolean defaultValue); + int getAttributeResourceValue(int index, int defaultValue); + int getAttributeIntValue(int index, int defaultValue); + int getAttributeUnsignedIntValue(int index, int defaultValue); + float getAttributeFloatValue(int index, float defaultValue); + String getIdAttribute(); + String getClassAttribute(); + int getIdAttributeResourceValue(int index); + int getStyleAttribute(); + String getAttributeValue(String namespace, String attribute); + int getAttributeListValue(String namespace, String attribute, String options[], int defaultValue); + boolean getAttributeBooleanValue(String namespace, String attribute, boolean defaultValue); + int getAttributeResourceValue(String namespace, String attribute, int defaultValue); + int getAttributeIntValue(String namespace, String attribute, int defaultValue); + int getAttributeUnsignedIntValue(String namespace, String attribute, int defaultValue); + float getAttributeFloatValue(String namespace, String attribute, float defaultValue); + + //TODO: remove + int getAttributeValueType(int index); + int getAttributeValueData(int index); +} diff --git a/axmlprinter/src/main/java/wind/android/util/TypedValue.java b/axmlprinter/src/main/java/wind/android/util/TypedValue.java new file mode 100644 index 0000000..f96e2d8 --- /dev/null +++ b/axmlprinter/src/main/java/wind/android/util/TypedValue.java @@ -0,0 +1,71 @@ +/* + * Copyright 2008 Android4ME + * + * 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 wind.android.util; + +/** + * @author Dmitry Skiba + * + */ +public class TypedValue { + + public int type; + public CharSequence string; + public int data; + public int assetCookie; + public int resourceId; + public int changingConfigurations; + + public static final int + TYPE_NULL =0, + TYPE_REFERENCE =1, + TYPE_ATTRIBUTE =2, + TYPE_STRING =3, + TYPE_FLOAT =4, + TYPE_DIMENSION =5, + TYPE_FRACTION =6, + TYPE_FIRST_INT =16, + TYPE_INT_DEC =16, + TYPE_INT_HEX =17, + TYPE_INT_BOOLEAN =18, + TYPE_FIRST_COLOR_INT =28, + TYPE_INT_COLOR_ARGB8 =28, + TYPE_INT_COLOR_RGB8 =29, + TYPE_INT_COLOR_ARGB4 =30, + TYPE_INT_COLOR_RGB4 =31, + TYPE_LAST_COLOR_INT =31, + TYPE_LAST_INT =31; + + public static final int + COMPLEX_UNIT_PX =0, + COMPLEX_UNIT_DIP =1, + COMPLEX_UNIT_SP =2, + COMPLEX_UNIT_PT =3, + COMPLEX_UNIT_IN =4, + COMPLEX_UNIT_MM =5, + COMPLEX_UNIT_SHIFT =0, + COMPLEX_UNIT_MASK =15, + COMPLEX_UNIT_FRACTION =0, + COMPLEX_UNIT_FRACTION_PARENT=1, + COMPLEX_RADIX_23p0 =0, + COMPLEX_RADIX_16p7 =1, + COMPLEX_RADIX_8p15 =2, + COMPLEX_RADIX_0p23 =3, + COMPLEX_RADIX_SHIFT =4, + COMPLEX_RADIX_MASK =3, + COMPLEX_MANTISSA_SHIFT =8, + COMPLEX_MANTISSA_MASK =0xFFFFFF; + +} diff --git a/axmlprinter/src/main/java/wind/test/AXMLPrinter.java b/axmlprinter/src/main/java/wind/test/AXMLPrinter.java new file mode 100644 index 0000000..d56063b --- /dev/null +++ b/axmlprinter/src/main/java/wind/test/AXMLPrinter.java @@ -0,0 +1,170 @@ +/* + * Copyright 2008 Android4ME + * + * 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 wind.test; + +import java.io.FileInputStream; +import wind.v1.XmlPullParser; +import wind.android.content.res.AXmlResourceParser; +//import android.util.TypedValue; + +/** + * @author Dmitry Skiba + * + * This is example usage of AXMLParser class. + * + * Prints xml document from Android's binary xml file. + */ +public class AXMLPrinter { + + public static void main(String[] arguments) { + if (arguments.length<1) { + log("Usage: AXMLPrinter "); + return; + } + try { + AXmlResourceParser parser=new AXmlResourceParser(); + parser.open(new FileInputStream(arguments[0])); + StringBuilder indent=new StringBuilder(10); + final String indentStep=" "; + while (true) { + int type=parser.next(); + if (type==XmlPullParser.END_DOCUMENT) { + break; + } + switch (type) { + case XmlPullParser.START_DOCUMENT: + { + log(""); + break; + } + case XmlPullParser.START_TAG: + { + log("%s<%s%s",indent, + getNamespacePrefix(parser.getPrefix()),parser.getName()); + indent.append(indentStep); + + int namespaceCountBefore=parser.getNamespaceCount(parser.getDepth()-1); + int namespaceCount=parser.getNamespaceCount(parser.getDepth()); + for (int i=namespaceCountBefore;i!=namespaceCount;++i) { + log("%sxmlns:%s=\"%s\"", + indent, + parser.getNamespacePrefix(i), + parser.getNamespaceUri(i)); + } + + for (int i=0;i!=parser.getAttributeCount();++i) { + log("%s%s%s=\"%s\"",indent, + getNamespacePrefix(parser.getAttributePrefix(i)), + parser.getAttributeName(i), + getAttributeValue(parser,i)); + } + log("%s>",indent); + break; + } + case XmlPullParser.END_TAG: + { + indent.setLength(indent.length()-indentStep.length()); + log("%s",indent, + getNamespacePrefix(parser.getPrefix()), + parser.getName()); + break; + } + case XmlPullParser.TEXT: + { + log("%s%s",indent,parser.getText()); + break; + } + } + } + } + catch (Exception e) { + e.printStackTrace(); + } + } + + private static String getNamespacePrefix(String prefix) { + if (prefix==null || prefix.length()==0) { + return ""; + } + return prefix+":"; + } + + private static String getAttributeValue(AXmlResourceParser parser,int index) { + int type=parser.getAttributeValueType(index); + int data=parser.getAttributeValueData(index); +// if (type==TypedValue.TYPE_STRING) { +// return parser.getAttributeValue(index); +// } +// if (type==TypedValue.TYPE_ATTRIBUTE) { +// return String.format("?%s%08X",getPackage(data),data); +// } +// if (type==TypedValue.TYPE_REFERENCE) { +// return String.format("@%s%08X",getPackage(data),data); +// } +// if (type==TypedValue.TYPE_FLOAT) { +// return String.valueOf(Float.intBitsToFloat(data)); +// } +// if (type==TypedValue.TYPE_INT_HEX) { +// return String.format("0x%08X",data); +// } +// if (type==TypedValue.TYPE_INT_BOOLEAN) { +// return data!=0?"true":"false"; +// } +// if (type==TypedValue.TYPE_DIMENSION) { +// return Float.toString(complexToFloat(data))+ +// DIMENSION_UNITS[data & TypedValue.COMPLEX_UNIT_MASK]; +// } +// if (type==TypedValue.TYPE_FRACTION) { +// return Float.toString(complexToFloat(data))+ +// FRACTION_UNITS[data & TypedValue.COMPLEX_UNIT_MASK]; +// } +// if (type>=TypedValue.TYPE_FIRST_COLOR_INT && type<=TypedValue.TYPE_LAST_COLOR_INT) { +// return String.format("#%08X",data); +// } +// if (type>=TypedValue.TYPE_FIRST_INT && type<=TypedValue.TYPE_LAST_INT) { +// return String.valueOf(data); +// } + return String.format("<0x%X, type 0x%02X>",data,type); + } + + private static String getPackage(int id) { + if (id>>>24==1) { + return "android:"; + } + return ""; + } + + private static void log(String format,Object...arguments) { + System.out.printf(format,arguments); + System.out.println(); + } + + /////////////////////////////////// ILLEGAL STUFF, DONT LOOK :) + + public static float complexToFloat(int complex) { + return (float)(complex & 0xFFFFFF00)*RADIX_MULTS[(complex>>4) & 3]; + } + + private static final float RADIX_MULTS[]={ + 0.00390625F,3.051758E-005F,1.192093E-007F,4.656613E-010F + }; + private static final String DIMENSION_UNITS[]={ + "px","dip","sp","pt","in","mm","","" + }; + private static final String FRACTION_UNITS[]={ + "%","%p","","","","","","" + }; +} \ No newline at end of file diff --git a/axmlprinter/src/main/java/wind/v1/XmlPullParser.java b/axmlprinter/src/main/java/wind/v1/XmlPullParser.java new file mode 100644 index 0000000..ba47c03 --- /dev/null +++ b/axmlprinter/src/main/java/wind/v1/XmlPullParser.java @@ -0,0 +1,1119 @@ +/* -*- c-basic-offset: 4; indent-tabs-mode: nil; -*- //------100-columns-wide------>|*/ +// for license please see accompanying LICENSE.txt file (available also at http://www.xmlpull.org/) + +package wind.v1; + +import wind.v1.XmlPullParserException; +import wind.v1.XmlPullParserFactory; + +import java.io.InputStream; +import java.io.IOException; +import java.io.Reader; + +/** + * XML Pull Parser is an interface that defines parsing functionlity provided + * in XMLPULL V1 API (visit this website to + * learn more about API and its implementations). + * + *

There are following different + * kinds of parser depending on which features are set:

    + *
  • non-validating parser as defined in XML 1.0 spec when + * FEATURE_PROCESS_DOCDECL is set to true + *
  • validating parser as defined in XML 1.0 spec when + * FEATURE_VALIDATION is true (and that implies that FEATURE_PROCESS_DOCDECL is true) + *
  • when FEATURE_PROCESS_DOCDECL is false (this is default and + * if different value is required necessary must be changed before parsing is started) + * then parser behaves like XML 1.0 compliant non-validating parser under condition that + * no DOCDECL is present in XML documents + * (internal entites can still be defined with defineEntityReplacementText()). + * This mode of operation is intened for operation in constrained environments such as J2ME. + *
+ * + * + *

There are two key methods: next() and nextToken(). While next() provides + * access to high level parsing events, nextToken() allows access to lower + * level tokens. + * + *

The current event state of the parser + * can be determined by calling the + * getEventType() method. + * Initially, the parser is in the START_DOCUMENT + * state. + * + *

The method next() advances the parser to the + * next event. The int value returned from next determines the current parser + * state and is identical to the value returned from following calls to + * getEventType (). + * + *

Th following event types are seen by next()

+ *
START_TAG
An XML start tag was read. + *
TEXT
Text content was read; + * the text content can be retreived using the getText() method. + * (when in validating mode next() will not report ignorable whitespaces, use nextToken() instead) + *
END_TAG
An end tag was read + *
END_DOCUMENT
No more events are available + *
+ * + *

after first next() or nextToken() (or any other next*() method) + * is called user application can obtain + * XML version, standalone and encoding from XML declaration + * in following ways:

    + *
  • version: + * getProperty("http://xmlpull.org/v1/doc/properties.html#xmldecl-version") + * returns String ("1.0") or null if XMLDecl was not read or if property is not supported + *
  • standalone: + * getProperty("http://xmlpull.org/v1/doc/features.html#xmldecl-standalone") + * returns Boolean: null if there was no standalone declaration + * or if property is not supported + * otherwise returns Boolean(true) if standalon="yes" and Boolean(false) when standalone="no" + *
  • encoding: obtained from getInputEncoding() + * null if stream had unknown encoding (not set in setInputStream) + * and it was not declared in XMLDecl + *
+ * + * A minimal example for using this API may look as follows: + *
+ * import java.io.IOException;
+ * import java.io.StringReader;
+ *
+ * import wind.v1.XmlPullParser;
+ * import wind.v1.XmlPullParserException.html;
+ * import wind.v1.XmlPullParserFactory;
+ *
+ * public class SimpleXmlPullApp
+ * {
+ *
+ *     public static void main (String args[])
+ *         throws XmlPullParserException, IOException
+ *     {
+ *         XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
+ *         factory.setNamespaceAware(true);
+ *         XmlPullParser xpp = factory.newPullParser();
+ *
+ *         xpp.setInput( new StringReader ( "<foo>Hello World!</foo>" ) );
+ *         int eventType = xpp.getEventType();
+ *         while (eventType != XmlPullParser.END_DOCUMENT) {
+ *          if(eventType == XmlPullParser.START_DOCUMENT) {
+ *              System.out.println("Start document");
+ *          } else if(eventType == XmlPullParser.END_DOCUMENT) {
+ *              System.out.println("End document");
+ *          } else if(eventType == XmlPullParser.START_TAG) {
+ *              System.out.println("Start tag "+xpp.getName());
+ *          } else if(eventType == XmlPullParser.END_TAG) {
+ *              System.out.println("End tag "+xpp.getName());
+ *          } else if(eventType == XmlPullParser.TEXT) {
+ *              System.out.println("Text "+xpp.getText());
+ *          }
+ *          eventType = xpp.next();
+ *         }
+ *     }
+ * }
+ * 
+ * + *

The above example will generate the following output: + *

+ * Start document
+ * Start tag foo
+ * Text Hello World!
+ * End tag foo
+ * 
+ * + *

For more details on API usage, please refer to the + * quick Introduction available at http://www.xmlpull.org + * + * @see XmlPullParserFactory + * @see #defineEntityReplacementText + * @see #getName + * @see #getNamespace + * @see #getText + * @see #next + * @see #nextToken + * @see #setInput + * @see #FEATURE_PROCESS_DOCDECL + * @see #FEATURE_VALIDATION + * @see #START_DOCUMENT + * @see #START_TAG + * @see #TEXT + * @see #END_TAG + * @see #END_DOCUMENT + * + * @author Stefan Haustein + * @author Aleksander Slominski + */ + +public interface XmlPullParser { + + /** This constant represents the default namespace (empty string "") */ + String NO_NAMESPACE = ""; + + // ---------------------------------------------------------------------------- + // EVENT TYPES as reported by next() + + /** + * Signalize that parser is at the very beginning of the document + * and nothing was read yet. + * This event type can only be observed by calling getEvent() + * before the first call to next(), nextToken, or nextTag()). + * + * @see #next + * @see #nextToken + */ + int START_DOCUMENT = 0; + + /** + * Logical end of the xml document. Returned from getEventType, next() + * and nextToken() + * when the end of the input document has been reached. + *

NOTE: calling again + * next() or nextToken() + * will result in exception being thrown. + * + * @see #next + * @see #nextToken + */ + int END_DOCUMENT = 1; + + /** + * Returned from getEventType(), + * next(), nextToken() when + * a start tag was read. + * The name of start tag is available from getName(), its namespace and prefix are + * available from getNamespace() and getPrefix() + * if namespaces are enabled. + * See getAttribute* methods to retrieve element attributes. + * See getNamespace* methods to retrieve newly declared namespaces. + * + * @see #next + * @see #nextToken + * @see #getName + * @see #getPrefix + * @see #getNamespace + * @see #getAttributeCount + * @see #getDepth + * @see #getNamespaceCount + * @see #getNamespace + * @see #FEATURE_PROCESS_NAMESPACES + */ + int START_TAG = 2; + + /** + * Returned from getEventType(), next(), or + * nextToken() when an end tag was read. + * The name of start tag is available from getName(), its + * namespace and prefix are + * available from getNamespace() and getPrefix(). + * + * @see #next + * @see #nextToken + * @see #getName + * @see #getPrefix + * @see #getNamespace + * @see #FEATURE_PROCESS_NAMESPACES + */ + int END_TAG = 3; + + + /** + * Character data was read and will is available by calling getText(). + *

Please note: next() will + * accumulate multiple + * events into one TEXT event, skipping IGNORABLE_WHITESPACE, + * PROCESSING_INSTRUCTION and COMMENT events, + * In contrast, nextToken() will stop reading + * text when any other event is observed. + * Also, when the state was reached by calling next(), the text value will + * be normalized, whereas getText() will + * return unnormalized content in the case of nextToken(). This allows + * an exact roundtrip without chnanging line ends when examining low + * level events, whereas for high level applications the text is + * normalized apropriately. + * + * @see #next + * @see #nextToken + * @see #getText + */ + int TEXT = 4; + + // ---------------------------------------------------------------------------- + // additional events exposed by lower level nextToken() + + /** + * A CDATA sections was just read; + * this token is available only from calls to nextToken(). + * A call to next() will accumulate various text events into a single event + * of type TEXT. The text contained in the CDATA section is available + * by callling getText(). + * + * @see #nextToken + * @see #getText + */ + int CDSECT = 5; + + /** + * An entity reference was just read; + * this token is available from nextToken() + * only. The entity name is available by calling getName(). If available, + * the replacement text can be obtained by calling getTextt(); otherwise, + * the user is responsibile for resolving the entity reference. + * This event type is never returned from next(); next() will + * accumulate the replacement text and other text + * events to a single TEXT event. + * + * @see #nextToken + * @see #getText + */ + int ENTITY_REF = 6; + + /** + * Ignorable whitespace was just read. + * This token is available only from nextToken()). + * For non-validating + * parsers, this event is only reported by nextToken() when outside + * the root element. + * Validating parsers may be able to detect ignorable whitespace at + * other locations. + * The ignorable whitespace string is available by calling getText() + * + *

NOTE: this is different from calling the + * isWhitespace() method, since text content + * may be whitespace but not ignorable. + * + * Ignorable whitespace is skipped by next() automatically; this event + * type is never returned from next(). + * + * @see #nextToken + * @see #getText + */ + int IGNORABLE_WHITESPACE = 7; + + /** + * An XML processing instruction declaration was just read. This + * event type is available only via nextToken(). + * getText() will return text that is inside the processing instruction. + * Calls to next() will skip processing instructions automatically. + * @see #nextToken + * @see #getText + */ + int PROCESSING_INSTRUCTION = 8; + + /** + * An XML comment was just read. This event type is this token is + * available via nextToken() only; + * calls to next() will skip comments automatically. + * The content of the comment can be accessed using the getText() + * method. + * + * @see #nextToken + * @see #getText + */ + int COMMENT = 9; + + /** + * An XML document type declaration was just read. This token is + * available from nextToken() only. + * The unparsed text inside the doctype is available via + * the getText() method. + * + * @see #nextToken + * @see #getText + */ + int DOCDECL = 10; + + /** + * This array can be used to convert the event type integer constants + * such as START_TAG or TEXT to + * to a string. For example, the value of TYPES[START_TAG] is + * the string "START_TAG". + * + * This array is intended for diagnostic output only. Relying + * on the contents of the array may be dangerous since malicous + * applications may alter the array, although it is final, due + * to limitations of the Java language. + */ + String [] TYPES = { + "START_DOCUMENT", + "END_DOCUMENT", + "START_TAG", + "END_TAG", + "TEXT", + "CDSECT", + "ENTITY_REF", + "IGNORABLE_WHITESPACE", + "PROCESSING_INSTRUCTION", + "COMMENT", + "DOCDECL" + }; + + + // ---------------------------------------------------------------------------- + // namespace related features + + /** + * This feature determines whether the parser processes + * namespaces. As for all features, the default value is false. + *

NOTE: The value can not be changed during + * parsing an must be set before parsing. + * + * @see #getFeature + * @see #setFeature + */ + String FEATURE_PROCESS_NAMESPACES = + "http://xmlpull.org/v1/doc/features.html#process-namespaces"; + + /** + * This feature determines whether namespace attributes are + * exposed via the attribute access methods. Like all features, + * the default value is false. This feature cannot be changed + * during parsing. + * + * @see #getFeature + * @see #setFeature + */ + String FEATURE_REPORT_NAMESPACE_ATTRIBUTES = + "http://xmlpull.org/v1/doc/features.html#report-namespace-prefixes"; + + /** + * This feature determines whether the document declaration + * is processed. If set to false, + * the DOCDECL event type is reported by nextToken() + * and ignored by next(). + * + * If this featue is activated, then the document declaration + * must be processed by the parser. + * + *

Please note: If the document type declaration + * was ignored, entity references may cause exceptions + * later in the parsing process. + * The default value of this feature is false. It cannot be changed + * during parsing. + * + * @see #getFeature + * @see #setFeature + */ + String FEATURE_PROCESS_DOCDECL = + "http://xmlpull.org/v1/doc/features.html#process-docdecl"; + + /** + * If this feature is activated, all validation errors as + * defined in the XML 1.0 sepcification are reported. + * This implies that FEATURE_PROCESS_DOCDECL is true and both, the + * internal and external document type declaration will be processed. + *

Please Note: This feature can not be changed + * during parsing. The default value is false. + * + * @see #getFeature + * @see #setFeature + */ + String FEATURE_VALIDATION = + "http://xmlpull.org/v1/doc/features.html#validation"; + + /** + * Use this call to change the general behaviour of the parser, + * such as namespace processing or doctype declaration handling. + * This method must be called before the first call to next or + * nextToken. Otherwise, an exception is thrown. + *

Example: call setFeature(FEATURE_PROCESS_NAMESPACES, true) in order + * to switch on namespace processing. The initial settings correspond + * to the properties requested from the XML Pull Parser factory. + * If none were requested, all feautures are deactivated by default. + * + * @exception XmlPullParserException If the feature is not supported or can not be set + * @exception IllegalArgumentException If string with the feature name is null + */ + void setFeature(String name, + boolean state) throws XmlPullParserException; + + /** + * Returns the current value of the given feature. + *

Please note: unknown features are + * always returned as false. + * + * @param name The name of feature to be retrieved. + * @return The value of the feature. + * @exception IllegalArgumentException if string the feature name is null + */ + + boolean getFeature(String name); + + /** + * Set the value of a property. + * + * The property name is any fully-qualified URI. + * + * @exception XmlPullParserException If the property is not supported or can not be set + * @exception IllegalArgumentException If string with the property name is null + */ + void setProperty(String name, + Object value) throws XmlPullParserException; + + /** + * Look up the value of a property. + * + * The property name is any fully-qualified URI. + *

NOTE: unknown properties are always + * returned as null. + * + * @param name The name of property to be retrieved. + * @return The value of named property. + */ + Object getProperty(String name); + + + /** + * Set the input source for parser to the given reader and + * resets the parser. The event type is set to the initial value + * START_DOCUMENT. + * Setting the reader to null will just stop parsing and + * reset parser state, + * allowing the parser to free internal resources + * such as parsing buffers. + */ + void setInput(Reader in) throws XmlPullParserException; + + + /** + * Sets the input stream the parser is going to process. + * This call resets the parser state and sets the event type + * to the initial value START_DOCUMENT. + * + *

NOTE: If an input encoding string is passed, + * it MUST be used. Otherwise, + * if inputEncoding is null, the parser SHOULD try to determine + * input encoding following XML 1.0 specification (see below). + * If encoding detection is supported then following feature + * http://xmlpull.org/v1/doc/features.html#detect-encoding + * MUST be true amd otherwise it must be false + * + * @param inputStream contains a raw byte input stream of possibly + * unknown encoding (when inputEncoding is null). + * + * @param inputEncoding if not null it MUST be used as encoding for inputStream + */ + void setInput(InputStream inputStream, String inputEncoding) + throws XmlPullParserException; + + /** + * Returns the input encoding if known, null otherwise. + * If setInput(InputStream, inputEncoding) was called with an inputEncoding + * value other than null, this value must be returned + * from this method. Otherwise, if inputEncoding is null and + * the parser suppports the encoding detection feature + * (http://xmlpull.org/v1/doc/features.html#detect-encoding), + * it must return the detected encoding. + * If setInput(Reader) was called, null is returned. + * After first call to next if XML declaration was present this method + * will return encoding declared. + */ + String getInputEncoding(); + + /** + * Set new value for entity replacement text as defined in + * XML 1.0 Section 4.5 + * Construction of Internal Entity Replacement Text. + * If FEATURE_PROCESS_DOCDECL or FEATURE_VALIDATION are set, calling this + * function will result in an exception -- when processing of DOCDECL is + * enabled, there is no need to the entity replacement text manually. + * + *

The motivation for this function is to allow very small + * implementations of XMLPULL that will work in J2ME environments. + * Though these implementations may not be able to process the document type + * declaration, they still can work with known DTDs by using this function. + * + *

Please notes: The given value is used literally as replacement text + * and it corresponds to declaring entity in DTD that has all special characters + * escaped: left angle bracket is replaced with &lt;, ampersnad with &amp; + * and so on. + * + *

Note: The given value is the literal replacement text and must not + * contain any other entity reference (if it contains any entity reference + * there will be no further replacement). + * + *

Note: The list of pre-defined entity names will + * always contain standard XML entities such as + * amp (&amp;), lt (&lt;), gt (&gt;), quot (&quot;), and apos (&apos;). + * Those cannot be redefined by this method! + * + * @see #setInput + * @see #FEATURE_PROCESS_DOCDECL + * @see #FEATURE_VALIDATION + */ + void defineEntityReplacementText(String entityName, + String replacementText) throws XmlPullParserException; + + /** + * Returns the numbers of elements in the namespace stack for the given + * depth. + * If namespaces are not enabled, 0 is returned. + * + *

NOTE: when parser is on END_TAG then it is allowed to call + * this function with getDepth()+1 argument to retrieve position of namespace + * prefixes and URIs that were declared on corresponding START_TAG. + *

NOTE: to retrieve lsit of namespaces declared in current element:

+     *       XmlPullParser pp = ...
+     *       int nsStart = pp.getNamespaceCount(pp.getDepth()-1);
+     *       int nsEnd = pp.getNamespaceCount(pp.getDepth());
+     *       for (int i = nsStart; i < nsEnd; i++) {
+     *          String prefix = pp.getNamespacePrefix(i);
+     *          String ns = pp.getNamespaceUri(i);
+     *           // ...
+     *      }
+     * 
+ * + * @see #getNamespacePrefix + * @see #getNamespaceUri + * @see #getNamespace() + * @see #getNamespace(String) + */ + int getNamespaceCount(int depth) throws XmlPullParserException; + + /** + * Returns the namespace prefixe for the given position + * in the namespace stack. + * Default namespace declaration (xmlns='...') will have null as prefix. + * If the given index is out of range, an exception is thrown. + *

Please note: when the parser is on an END_TAG, + * namespace prefixes that were declared + * in the corresponding START_TAG are still accessible + * although they are no longer in scope. + */ + String getNamespacePrefix(int pos) throws XmlPullParserException; + + /** + * Returns the namespace URI for the given position in the + * namespace stack + * If the position is out of range, an exception is thrown. + *

NOTE: when parser is on END_TAG then namespace prefixes that were declared + * in corresponding START_TAG are still accessible even though they are not in scope + */ + String getNamespaceUri(int pos) throws XmlPullParserException; + + /** + * Returns the URI corresponding to the given prefix, + * depending on current state of the parser. + * + *

If the prefix was not declared in the current scope, + * null is returned. The default namespace is included + * in the namespace table and is available via + * getNamespace (null). + * + *

This method is a convenience method for + * + *

+     *  for (int i = getNamespaceCount(getDepth ())-1; i >= 0; i--) {
+     *   if (getNamespacePrefix(i).equals( prefix )) {
+     *     return getNamespaceUri(i);
+     *   }
+     *  }
+     *  return null;
+     * 
+ * + *

Please note: parser implementations + * may provide more efifcient lookup, e.g. using a Hashtable. + * The 'xml' prefix is bound to "http://www.w3.org/XML/1998/namespace", as + * defined in the + * Namespaces in XML + * specification. Analogous, the 'xmlns' prefix is resolved to + * http://www.w3.org/2000/xmlns/ + * + * @see #getNamespaceCount + * @see #getNamespacePrefix + * @see #getNamespaceUri + */ + String getNamespace(String prefix); + + + // -------------------------------------------------------------------------- + // miscellaneous reporting methods + + /** + * Returns the current depth of the element. + * Outside the root element, the depth is 0. The + * depth is incremented by 1 when a start tag is reached. + * The depth is decremented AFTER the end tag + * event was observed. + * + *

+     * <!-- outside -->     0
+     * <root>                  1
+     *   sometext                 1
+     *     <foobar>         2
+     *     </foobar>        2
+     * </root>              1
+     * <!-- outside -->     0
+     * 
+ */ + int getDepth(); + + /** + * Returns a short text describing the current parser state, including + * the position, a + * description of the current event and the data source if known. + * This method is especially useful to provide meaningful + * error messages and for debugging purposes. + */ + String getPositionDescription(); + + + /** + * Returns the current line number, starting from 1. + * When the parser does not know the current line number + * or can not determine it, -1 is returned (e.g. for WBXML). + * + * @return current line number or -1 if unknown. + */ + int getLineNumber(); + + /** + * Returns the current column number, starting from 0. + * When the parser does not know the current column number + * or can not determine it, -1 is returned (e.g. for WBXML). + * + * @return current column number or -1 if unknown. + */ + int getColumnNumber(); + + + // -------------------------------------------------------------------------- + // TEXT related methods + + /** + * Checks whether the current TEXT event contains only whitespace + * characters. + * For IGNORABLE_WHITESPACE, this is always true. + * For TEXT and CDSECT, false is returned when the current event text + * contains at least one non-white space character. For any other + * event type an exception is thrown. + * + *

Please note: non-validating parsers are not + * able to distinguish whitespace and ignorable whitespace, + * except from whitespace outside the root element. Ignorable + * whitespace is reported as separate event, which is exposed + * via nextToken only. + * + */ + boolean isWhitespace() throws XmlPullParserException; + + /** + * Returns the text content of the current event as String. + * The value returned depends on current event type, + * for example for TEXT event it is element content + * (this is typical case when next() is used). + * + * See description of nextToken() for detailed description of + * possible returned values for different types of events. + * + *

NOTE: in case of ENTITY_REF, this method returns + * the entity replacement text (or null if not available). This is + * the only case where + * getText() and getTextCharacters() return different values. + * + * @see #getEventType + * @see #next + * @see #nextToken + */ + String getText(); + + + /** + * Returns the buffer that contains the text of the current event, + * as well as the start offset and length relevant for the current + * event. See getText(), next() and nextToken() for description of possible returned values. + * + *

Please note: this buffer must not + * be modified and its content MAY change after a call to + * next() or nextToken(). This method will always return the + * same value as getText(), except for ENTITY_REF. In the case + * of ENTITY ref, getText() returns the replacement text and + * this method returns the actual input buffer containing the + * entity name. + * If getText() returns null, this method returns null as well and + * the values returned in the holder array MUST be -1 (both start + * and length). + * + * @see #getText + * @see #next + * @see #nextToken + * + * @param holderForStartAndLength Must hold an 2-element int array + * into which the start offset and length values will be written. + * @return char buffer that contains the text of the current event + * (null if the current event has no text associated). + */ + char[] getTextCharacters(int[] holderForStartAndLength); + + // -------------------------------------------------------------------------- + // START_TAG / END_TAG shared methods + + /** + * Returns the namespace URI of the current element. + * The default namespace is represented + * as empty string. + * If namespaces are not enabled, an empty String ("") is always returned. + * The current event must be START_TAG or END_TAG; otherwise, + * null is returned. + */ + String getNamespace(); + + /** + * For START_TAG or END_TAG events, the (local) name of the current + * element is returned when namespaces are enabled. When namespace + * processing is disabled, the raw name is returned. + * For ENTITY_REF events, the entity name is returned. + * If the current event is not START_TAG, END_TAG, or ENTITY_REF, + * null is returned. + *

Please note: To reconstruct the raw element name + * when namespaces are enabled and the prefix is not null, + * you will need to add the prefix and a colon to localName.. + * + */ + String getName(); + + /** + * Returns the prefix of the current element. + * If the element is in the default namespace (has no prefix), + * null is returned. + * If namespaces are not enabled, or the current event + * is not START_TAG or END_TAG, null is returned. + */ + String getPrefix(); + + /** + * Returns true if the current event is START_TAG and the tag + * is degenerated + * (e.g. <foobar/>). + *

NOTE: if the parser is not on START_TAG, an exception + * will be thrown. + */ + boolean isEmptyElementTag() throws XmlPullParserException; + + // -------------------------------------------------------------------------- + // START_TAG Attributes retrieval methods + + /** + * Returns the number of attributes of the current start tag, or + * -1 if the current event type is not START_TAG + * + * @see #getAttributeNamespace + * @see #getAttributeName + * @see #getAttributePrefix + * @see #getAttributeValue + */ + int getAttributeCount(); + + /** + * Returns the namespace URI of the attribute + * with the given index (starts from 0). + * Returns an empty string ("") if namespaces are not enabled + * or the attribute has no namespace. + * Throws an IndexOutOfBoundsException if the index is out of range + * or the current event type is not START_TAG. + * + *

NOTE: if FEATURE_REPORT_NAMESPACE_ATTRIBUTES is set + * then namespace attributes (xmlns:ns='...') must be reported + * with namespace + * http://www.w3.org/2000/xmlns/ + * (visit this URL for description!). + * The default namespace attribute (xmlns="...") will be reported with empty namespace. + *

NOTE:The xml prefix is bound as defined in + * Namespaces in XML + * specification to "http://www.w3.org/XML/1998/namespace". + * + * @param zero based index of attribute + * @return attribute namespace, + * empty string ("") is returned if namesapces processing is not enabled or + * namespaces processing is enabled but attribute has no namespace (it has no prefix). + */ + String getAttributeNamespace(int index); + + /** + * Returns the local name of the specified attribute + * if namespaces are enabled or just attribute name if namespaces are disabled. + * Throws an IndexOutOfBoundsException if the index is out of range + * or current event type is not START_TAG. + * + * @param zero based index of attribute + * @return attribute name (null is never returned) + */ + String getAttributeName(int index); + + /** + * Returns the prefix of the specified attribute + * Returns null if the element has no prefix. + * If namespaces are disabled it will always return null. + * Throws an IndexOutOfBoundsException if the index is out of range + * or current event type is not START_TAG. + * + * @param zero based index of attribute + * @return attribute prefix or null if namespaces processing is not enabled. + */ + String getAttributePrefix(int index); + + /** + * Returns the type of the specified attribute + * If parser is non-validating it MUST return CDATA. + * + * @param zero based index of attribute + * @return attribute type (null is never returned) + */ + String getAttributeType(int index); + + /** + * Returns if the specified attribute was not in input was declared in XML. + * If parser is non-validating it MUST always return false. + * This information is part of XML infoset: + * + * @param zero based index of attribute + * @return false if attribute was in input + */ + boolean isAttributeDefault(int index); + + /** + * Returns the given attributes value. + * Throws an IndexOutOfBoundsException if the index is out of range + * or current event type is not START_TAG. + * + *

NOTE: attribute value must be normalized + * (including entity replacement text if PROCESS_DOCDECL is false) as described in + * XML 1.0 section + * 3.3.3 Attribute-Value Normalization + * + * @see #defineEntityReplacementText + * + * @param zero based index of attribute + * @return value of attribute (null is never returned) + */ + String getAttributeValue(int index); + + /** + * Returns the attributes value identified by namespace URI and namespace localName. + * If namespaces are disabled namespace must be null. + * If current event type is not START_TAG then IndexOutOfBoundsException will be thrown. + * + *

NOTE: attribute value must be normalized + * (including entity replacement text if PROCESS_DOCDECL is false) as described in + * XML 1.0 section + * 3.3.3 Attribute-Value Normalization + * + * @see #defineEntityReplacementText + * + * @param namespace Namespace of the attribute if namespaces are enabled otherwise must be null + * @param name If namespaces enabled local name of attribute otherwise just attribute name + * @return value of attribute or null if attribute with given name does not exist + */ + String getAttributeValue(String namespace, + String name); + + // -------------------------------------------------------------------------- + // actual parsing methods + + /** + * Returns the type of the current event (START_TAG, END_TAG, TEXT, etc.) + * + * @see #next() + * @see #nextToken() + */ + int getEventType() + throws XmlPullParserException; + + /** + * Get next parsing event - element content wil be coalesced and only one + * TEXT event must be returned for whole element content + * (comments and processing instructions will be ignored and emtity references + * must be expanded or exception mus be thrown if entity reerence can not be exapnded). + * If element content is empty (content is "") then no TEXT event will be reported. + * + *

NOTE: empty element (such as <tag/>) will be reported + * with two separate events: START_TAG, END_TAG - it must be so to preserve + * parsing equivalency of empty element to <tag></tag>. + * (see isEmptyElementTag ()) + * + * @see #isEmptyElementTag + * @see #START_TAG + * @see #TEXT + * @see #END_TAG + * @see #END_DOCUMENT + */ + + int next() + throws XmlPullParserException, IOException; + + + /** + * This method works similarly to next() but will expose + * additional event types (COMMENT, CDSECT, DOCDECL, ENTITY_REF, PROCESSING_INSTRUCTION, or + * IGNORABLE_WHITESPACE) if they are available in input. + * + *

If special feature + * FEATURE_XML_ROUNDTRIP + * (identified by URI: http://xmlpull.org/v1/doc/features.html#xml-roundtrip) + * is enabled it is possible to do XML document round trip ie. reproduce + * exectly on output the XML input using getText(): + * returned content is always unnormalized (exactly as in input). + * Otherwise returned content is end-of-line normalized as described + * XML 1.0 End-of-Line Handling + * and. Also when this feature is enabled exact content of START_TAG, END_TAG, + * DOCDECL and PROCESSING_INSTRUCTION is available. + * + *

Here is the list of tokens that can be returned from nextToken() + * and what getText() and getTextCharacters() returns:

+ *
START_DOCUMENT
null + *
END_DOCUMENT
null + *
START_TAG
null unless FEATURE_XML_ROUNDTRIP + * enabled and then returns XML tag, ex: <tag attr='val'> + *
END_TAG
null unless FEATURE_XML_ROUNDTRIP + * id enabled and then returns XML tag, ex: </tag> + *
TEXT
return element content. + *
Note: that element content may be delivered in multiple consecutive TEXT events. + *
IGNORABLE_WHITESPACE
return characters that are determined to be ignorable white + * space. If the FEATURE_XML_ROUNDTRIP is enabled all whitespace content outside root + * element will always reported as IGNORABLE_WHITESPACE otherise rteporting is optional. + *
Note: that element content may be delevered in multiple consecutive IGNORABLE_WHITESPACE events. + *
CDSECT
+ * return text inside CDATA + * (ex. 'fo<o' from <!CDATA[fo<o]]>) + *
PROCESSING_INSTRUCTION
+ * if FEATURE_XML_ROUNDTRIP is true + * return exact PI content ex: 'pi foo' from <?pi foo?> + * otherwise it may be exact PI content or concatenation of PI target, + * space and data so for example for + * <?target data?> string "target data" may + * be returned if FEATURE_XML_ROUNDTRIP is false. + *
COMMENT
return comment content ex. 'foo bar' from <!--foo bar--> + *
ENTITY_REF
getText() MUST return entity replacement text if PROCESS_DOCDECL is false + * otherwise getText() MAY return null, + * additionally getTextCharacters() MUST return entity name + * (for example 'entity_name' for &entity_name;). + *
NOTE: this is the only place where value returned from getText() and + * getTextCharacters() are different + *
NOTE: it is user responsibility to resolve entity reference + * if PROCESS_DOCDECL is false and there is no entity replacement text set in + * defineEntityReplacementText() method (getText() will be null) + *
NOTE: character entities (ex. &#32;) and standard entities such as + * &amp; &lt; &gt; &quot; &apos; are reported as well + * and are not reported as TEXT tokens but as ENTITY_REF tokens! + * This requirement is added to allow to do roundtrip of XML documents! + *
DOCDECL
+ * if FEATURE_XML_ROUNDTRIP is true or PROCESS_DOCDECL is false + * then return what is inside of DOCDECL for example it returns:
+     * " titlepage SYSTEM "http://www.foo.bar/dtds/typo.dtd"
+     * [<!ENTITY % active.links "INCLUDE">]"
+ *

for input document that contained:

+     * <!DOCTYPE titlepage SYSTEM "http://www.foo.bar/dtds/typo.dtd"
+     * [<!ENTITY % active.links "INCLUDE">]>
+ * otherwise if FEATURE_XML_ROUNDTRIP is false and PROCESS_DOCDECL is true + * then what is returned is undefined (it may be even null) + *
+ *
+ * + *

NOTE: there is no gurantee that there will only one TEXT or + * IGNORABLE_WHITESPACE event from nextToken() as parser may chose to deliver element content in + * multiple tokens (dividing element content into chunks) + * + *

NOTE: whether returned text of token is end-of-line normalized + * is depending on FEATURE_XML_ROUNDTRIP. + * + *

NOTE: XMLDecl (<?xml ...?>) is not reported but its content + * is available through optional properties (see class description above). + * + * @see #next + * @see #START_TAG + * @see #TEXT + * @see #END_TAG + * @see #END_DOCUMENT + * @see #COMMENT + * @see #DOCDECL + * @see #PROCESSING_INSTRUCTION + * @see #ENTITY_REF + * @see #IGNORABLE_WHITESPACE + */ + int nextToken() + throws XmlPullParserException, IOException; + + //----------------------------------------------------------------------------- + // utility methods to mak XML parsing easier ... + + /** + * Test if the current event is of the given type and if the + * namespace and name do match. null will match any namespace + * and any name. If the test is not passed, an exception is + * thrown. The exception text indicates the parser position, + * the expected event and the current event that is not meeting the + * requirement. + * + *

Essentially it does this + *

+     *  if (type != getEventType()
+     *  || (namespace != null &&  !namespace.equals( getNamespace () ) )
+     *  || (name != null &&  !name.equals( getName() ) ) )
+     *     throw new XmlPullParserException( "expected "+ TYPES[ type ]+getPositionDescription());
+     * 
+ */ + void require(int type, String namespace, String name) + throws XmlPullParserException, IOException; + + /** + * If current event is START_TAG then if next element is TEXT then element content is returned + * or if next event is END_TAG then empty string is returned, otherwise exception is thrown. + * After calling this function successfully parser will be positioned on END_TAG. + * + *

The motivation for this function is to allow to parse consistently both + * empty elements and elements that has non empty content, for example for input:

    + *
  1. <tag>foo</tag> + *
  2. <tag></tag> (which is equivalent to <tag/> + * both input can be parsed with the same code: + *
    +     *   p.nextTag()
    +     *   p.requireEvent(p.START_TAG, "", "tag");
    +     *   String content = p.nextText();
    +     *   p.requireEvent(p.END_TAG, "", "tag");
    +     * 
    + * This function together with nextTag make it very easy to parse XML that has + * no mixed content. + * + * + *

    Essentially it does this + *

    +     *  if(getEventType() != START_TAG) {
    +     *     throw new XmlPullParserException(
    +     *       "parser must be on START_TAG to read next text", this, null);
    +     *  }
    +     *  int eventType = next();
    +     *  if(eventType == TEXT) {
    +     *     String result = getText();
    +     *     eventType = next();
    +     *     if(eventType != END_TAG) {
    +     *       throw new XmlPullParserException(
    +     *          "event TEXT it must be immediately followed by END_TAG", this, null);
    +     *      }
    +     *      return result;
    +     *  } else if(eventType == END_TAG) {
    +     *     return "";
    +     *  } else {
    +     *     throw new XmlPullParserException(
    +     *       "parser must be on START_TAG or TEXT to read text", this, null);
    +     *  }
    +     * 
    + */ + String nextText() throws XmlPullParserException, IOException; + + /** + * Call next() and return event if it is START_TAG or END_TAG + * otherwise throw an exception. + * It will skip whitespace TEXT before actual tag if any. + * + *

    essentially it does this + *

    +     *   int eventType = next();
    +     *   if(eventType == TEXT &&  isWhitespace()) {   // skip whitespace
    +     *      eventType = next();
    +     *   }
    +     *   if (eventType != START_TAG &&  eventType != END_TAG) {
    +     *      throw new XmlPullParserException("expected start or end tag", this, null);
    +     *   }
    +     *   return eventType;
    +     * 
    + */ + int nextTag() throws XmlPullParserException, IOException; + +} + diff --git a/axmlprinter/src/main/java/wind/v1/XmlPullParserException.java b/axmlprinter/src/main/java/wind/v1/XmlPullParserException.java new file mode 100644 index 0000000..3523778 --- /dev/null +++ b/axmlprinter/src/main/java/wind/v1/XmlPullParserException.java @@ -0,0 +1,78 @@ +/* -*- c-basic-offset: 4; indent-tabs-mode: nil; -*- //------100-columns-wide------>|*/ +// for license please see accompanying LICENSE.txt file (available also at http://www.xmlpull.org/) + +package wind.v1; + +import wind.v1.XmlPullParser; + +/** + * This exception is thrown to signal XML Pull Parser related faults. + * + * @author Aleksander Slominski + */ +public class XmlPullParserException extends Exception { + protected Throwable detail; + protected int row = -1; + protected int column = -1; + + /* public XmlPullParserException() { + }*/ + + public XmlPullParserException(String s) { + super(s); + } + + /* + public XmlPullParserException(String s, Throwable thrwble) { + super(s); + this.detail = thrwble; + } + + public XmlPullParserException(String s, int row, int column) { + super(s); + this.row = row; + this.column = column; + } + */ + + public XmlPullParserException(String msg, XmlPullParser parser, Throwable chain) { + super ((msg == null ? "" : msg+" ") + + (parser == null ? "" : "(position:"+parser.getPositionDescription()+") ") + + (chain == null ? "" : "caused by: "+chain)); + + if (parser != null) { + this.row = parser.getLineNumber(); + this.column = parser.getColumnNumber(); + } + this.detail = chain; + } + + public Throwable getDetail() { return detail; } + // public void setDetail(Throwable cause) { this.detail = cause; } + public int getLineNumber() { return row; } + public int getColumnNumber() { return column; } + + /* + public String getMessage() { + if(detail == null) + return super.getMessage(); + else + return super.getMessage() + "; nested exception is: \n\t" + + detail.getMessage(); + } + */ + + //NOTE: code that prints this and detail is difficult in J2ME + public void printStackTrace() { + if (detail == null) { + super.printStackTrace(); + } else { + synchronized(System.err) { + System.err.println(super.getMessage() + "; nested exception is:"); + detail.printStackTrace(); + } + } + } + +} + diff --git a/axmlprinter/src/main/java/wind/v1/XmlPullParserFactory.java b/axmlprinter/src/main/java/wind/v1/XmlPullParserFactory.java new file mode 100644 index 0000000..e1465da --- /dev/null +++ b/axmlprinter/src/main/java/wind/v1/XmlPullParserFactory.java @@ -0,0 +1,360 @@ +/* -*- c-basic-offset: 4; indent-tabs-mode: nil; -*- //------100-columns-wide------>|*/ +// for license please see accompanying LICENSE.txt file (available also at http://www.xmlpull.org/) + +package wind.v1; + +import wind.v1.XmlPullParser; +import wind.v1.XmlPullParserException; +import wind.v1.XmlSerializer; + +import java.io.InputStream; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; + +/** + * This class is used to create implementations of XML Pull Parser defined in XMPULL V1 API. + * The name of actual factory class will be determined based on several parameters. + * It works similar to JAXP but tailored to work in J2ME environments + * (no access to system properties or file system) so name of parser class factory to use + * and its class used for loading (no class loader - on J2ME no access to context class loaders) + * must be passed explicitly. If no name of parser factory was passed (or is null) + * it will try to find name by searching in CLASSPATH for + * META-INF/services/wind.v1.XmlPullParserFactory resource that should contain + * a comma separated list of class names of factories or parsers to try (in order from + * left to the right). If none found, it will throw an exception. + * + *
    NOTE:In J2SE or J2EE environments, you may want to use + * newInstance(property, classLoaderCtx) + * where first argument is + * System.getProperty(XmlPullParserFactory.PROPERTY_NAME) + * and second is Thread.getContextClassLoader().getClass() . + * + * @see wind.v1.XmlPullParser + * + * @author Aleksander Slominski + * @author Stefan Haustein + */ + +public class XmlPullParserFactory { + /** used as default class to server as context class in newInstance() */ + final static Class referenceContextClass; + + static { + XmlPullParserFactory f = new XmlPullParserFactory(); + referenceContextClass = f.getClass(); + } + + /** Name of the system or midlet property that should be used for + a system property containing a comma separated list of factory + or parser class names (value: + wind.v1.XmlPullParserFactory). */ + + + public static final String PROPERTY_NAME = + "org.xmlpull.v1.XmlPullParserFactory"; + + private static final String RESOURCE_NAME = + "/META-INF/services/" + PROPERTY_NAME; + + + // public static final String DEFAULT_PROPERTY = + // "wind.xpp3.XmlPullParser,org.kxml2.io.KXmlParser"; + + + protected Vector parserClasses; + protected String classNamesLocation; + + protected Vector serializerClasses; + + + // features are kept there + protected Hashtable features = new Hashtable(); + + + /** + * Protected constructor to be called by factory implementations. + */ + + protected XmlPullParserFactory() { + } + + + + /** + * Set the features to be set when XML Pull Parser is created by this factory. + *

    NOTE: factory features are not used for XML Serializer. + * + * @param name string with URI identifying feature + * @param state if true feature will be set; if false will be ignored + */ + + public void setFeature(String name, + boolean state) throws XmlPullParserException { + + features.put(name, new Boolean(state)); + } + + + /** + * Return the current value of the feature with given name. + *

    NOTE: factory features are not used for XML Serializer. + * + * @param name The name of feature to be retrieved. + * @return The value of named feature. + * Unknown features are always returned as false + */ + + public boolean getFeature (String name) { + Boolean value = (Boolean) features.get(name); + return value != null ? value.booleanValue() : false; + } + + /** + * Specifies that the parser produced by this factory will provide + * support for XML namespaces. + * By default the value of this is set to false. + * + * @param awareness true if the parser produced by this code + * will provide support for XML namespaces; false otherwise. + */ + + public void setNamespaceAware(boolean awareness) { + features.put (wind.v1.XmlPullParser.FEATURE_PROCESS_NAMESPACES, new Boolean (awareness)); + } + + /** + * Indicates whether or not the factory is configured to produce + * parsers which are namespace aware + * (it simply set feature XmlPullParser.FEATURE_PROCESS_NAMESPACES to true or false). + * + * @return true if the factory is configured to produce parsers + * which are namespace aware; false otherwise. + */ + + public boolean isNamespaceAware() { + return getFeature (wind.v1.XmlPullParser.FEATURE_PROCESS_NAMESPACES); + } + + + /** + * Specifies that the parser produced by this factory will be validating + * (it simply set feature XmlPullParser.FEATURE_VALIDATION to true or false). + * + * By default the value of this is set to false. + * + * @param validating - if true the parsers created by this factory must be validating. + */ + + public void setValidating(boolean validating) { + features.put (wind.v1.XmlPullParser.FEATURE_VALIDATION, new Boolean (validating)); + } + + /** + * Indicates whether or not the factory is configured to produce parsers + * which validate the XML content during parse. + * + * @return true if the factory is configured to produce parsers + * which validate the XML content during parse; false otherwise. + */ + + public boolean isValidating() { + return getFeature (wind.v1.XmlPullParser.FEATURE_VALIDATION); + } + + /** + * Creates a new instance of a XML Pull Parser + * using the currently configured factory features. + * + * @return A new instance of a XML Pull Parser. + * @throws XmlPullParserException if a parser cannot be created which satisfies the + * requested configuration. + */ + + public wind.v1.XmlPullParser newPullParser() throws XmlPullParserException { + + if (parserClasses == null) throw new XmlPullParserException + ("Factory initialization was incomplete - has not tried "+classNamesLocation); + + if (parserClasses.size() == 0) throw new XmlPullParserException + ("No valid parser classes found in "+classNamesLocation); + + final StringBuffer issues = new StringBuffer (); + + for (int i = 0; i < parserClasses.size (); i++) { + final Class ppClass = (Class) parserClasses.elementAt (i); + try { + final wind.v1.XmlPullParser pp = (wind.v1.XmlPullParser) ppClass.newInstance(); + // if( ! features.isEmpty() ) { + //Enumeration keys = features.keys(); + // while(keys.hasMoreElements()) { + + for (Enumeration e = features.keys (); e.hasMoreElements ();) { + final String key = (String) e.nextElement(); + final Boolean value = (Boolean) features.get(key); + if(value != null && value.booleanValue()) { + pp.setFeature(key, true); + } + } + return pp; + + } catch(Exception ex) { + issues.append (ppClass.getName () + ": "+ ex.toString ()+"; "); + } + } + + throw new XmlPullParserException ("could not create parser: "+issues); + } + + + /** + * Creates a new instance of a XML Serializer. + * + *

    NOTE: factory features are not used for XML Serializer. + * + * @return A new instance of a XML Serializer. + * @throws XmlPullParserException if a parser cannot be created which satisfies the + * requested configuration. + */ + + public wind.v1.XmlSerializer newSerializer() throws XmlPullParserException { + + if (serializerClasses == null) { + throw new XmlPullParserException + ("Factory initialization incomplete - has not tried "+classNamesLocation); + } + if(serializerClasses.size() == 0) { + throw new XmlPullParserException + ("No valid serializer classes found in "+classNamesLocation); + } + + final StringBuffer issues = new StringBuffer (); + + for (int i = 0; i < serializerClasses.size (); i++) { + final Class ppClass = (Class) serializerClasses.elementAt (i); + try { + final wind.v1.XmlSerializer ser = (wind.v1.XmlSerializer) ppClass.newInstance(); + + // for (Enumeration e = features.keys (); e.hasMoreElements ();) { + // String key = (String) e.nextElement(); + // Boolean value = (Boolean) features.get(key); + // if(value != null && value.booleanValue()) { + // ser.setFeature(key, true); + // } + // } + return ser; + + } catch(Exception ex) { + issues.append (ppClass.getName () + ": "+ ex.toString ()+"; "); + } + } + + throw new XmlPullParserException ("could not create serializer: "+issues); + } + + /** + * Create a new instance of a PullParserFactory that can be used + * to create XML pull parsers (see class description for more + * details). + * + * @return a new instance of a PullParserFactory, as returned by newInstance (null, null); + */ + public static XmlPullParserFactory newInstance () throws XmlPullParserException { + return newInstance(null, null); + } + + public static XmlPullParserFactory newInstance (String classNames, Class context) + throws XmlPullParserException { + + if (context == null) { + //NOTE: make sure context uses the same class loader as API classes + // this is the best we can do without having access to context classloader in J2ME + // if API is in the same classloader as implementation then this will work + context = referenceContextClass; + } + + String classNamesLocation = null; + + if (classNames == null || classNames.length() == 0 || "DEFAULT".equals(classNames)) { + try { + InputStream is = context.getResourceAsStream (RESOURCE_NAME); + + if (is == null) throw new XmlPullParserException + ("resource not found: "+RESOURCE_NAME + +" make sure that parser implementing XmlPull API is available"); + final StringBuffer sb = new StringBuffer(); + + while (true) { + final int ch = is.read(); + if (ch < 0) break; + else if (ch > ' ') + sb.append((char) ch); + } + is.close (); + + classNames = sb.toString (); + } + catch (Exception e) { + throw new XmlPullParserException (null, null, e); + } + classNamesLocation = "resource "+RESOURCE_NAME+" that contained '"+classNames+"'"; + } else { + classNamesLocation = + "parameter classNames to newInstance() that contained '"+classNames+"'"; + } + + XmlPullParserFactory factory = null; + final Vector parserClasses = new Vector (); + final Vector serializerClasses = new Vector (); + int pos = 0; + + while (pos < classNames.length ()) { + int cut = classNames.indexOf (',', pos); + + if (cut == -1) cut = classNames.length (); + final String name = classNames.substring (pos, cut); + + Class candidate = null; + Object instance = null; + + try { + candidate = Class.forName (name); + // necessary because of J2ME .class issue + instance = candidate.newInstance (); + } + catch (Exception e) {} + + if (candidate != null) { + boolean recognized = false; + if (instance instanceof XmlPullParser) { + parserClasses.addElement (candidate); + recognized = true; + } + if (instance instanceof XmlSerializer) { + serializerClasses.addElement (candidate); + recognized = true; + } + if (instance instanceof XmlPullParserFactory) { + if (factory == null) { + factory = (XmlPullParserFactory) instance; + } + recognized = true; + } + if (!recognized) { + throw new XmlPullParserException ("incompatible class: "+name); + } + } + pos = cut + 1; + } + + if (factory == null) { + factory = new XmlPullParserFactory (); + } + factory.parserClasses = parserClasses; + factory.serializerClasses = serializerClasses; + factory.classNamesLocation = classNamesLocation; + return factory; + } +} + + diff --git a/axmlprinter/src/main/java/wind/v1/XmlSerializer.java b/axmlprinter/src/main/java/wind/v1/XmlSerializer.java new file mode 100644 index 0000000..c176123 --- /dev/null +++ b/axmlprinter/src/main/java/wind/v1/XmlSerializer.java @@ -0,0 +1,326 @@ +package wind.v1; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.Writer; + +/** + * Define an interface to serialziation of XML Infoset. + * This interface abstracts away if serialized XML is XML 1.0 comaptible text or + * other formats of XML 1.0 serializations (such as binary XML for example with WBXML). + * + *

    PLEASE NOTE: This interface will be part of XmlPull 1.2 API. + * It is included as basis for discussion. It may change in any way. + * + *

    Exceptions that may be thrown are: IOException or runtime exception + * (more runtime exceptions can be thrown but are not declared and as such + * have no semantics defined for this interface): + *

      + *
    • IllegalArgumentException - for almost all methods to signal that + * argument is illegal + *
    • IllegalStateException - to signal that call has good arguments but + * is not expected here (violation of contract) and for features/properties + * when requesting setting unimplemented feature/property + * (UnsupportedOperationException would be better but it is not in MIDP) + *
    + * + *

    NOTE: writing CDSECT, ENTITY_REF, IGNORABLE_WHITESPACE, + * PROCESSING_INSTRUCTION, COMMENT, and DOCDECL in some implementations + * may not be supported (for example when serializing to WBXML). + * In such case IllegalStateException will be thrown and it is recommened + * to use an optional feature to signal that implementation is not + * supporting this kind of output. + */ + +public interface XmlSerializer { + + /** + * Set feature identified by name (recommended to be URI for uniqueness). + * Some well known optional features are defined in + * + * http://www.xmlpull.org/v1/doc/features.html. + * + * If feature is not recocgnized or can not be set + * then IllegalStateException MUST be thrown. + * + * @exception IllegalStateException If the feature is not supported or can not be set + */ + void setFeature(String name, + boolean state) + throws IllegalArgumentException, IllegalStateException; + + + /** + * Return the current value of the feature with given name. + *

    NOTE: unknown properties are always returned as null + * + * @param name The name of feature to be retrieved. + * @return The value of named feature. + * @exception IllegalArgumentException if feature string is null + */ + boolean getFeature(String name); + + + /** + * Set the value of a property. + * (the property name is recommened to be URI for uniqueness). + * Some well known optional properties are defined in + * + * http://www.xmlpull.org/v1/doc/properties.html. + * + * If property is not recocgnized or can not be set + * then IllegalStateException MUST be thrown. + * + * @exception IllegalStateException if the property is not supported or can not be set + */ + void setProperty(String name, + Object value) + throws IllegalArgumentException, IllegalStateException; + + /** + * Look up the value of a property. + * + * The property name is any fully-qualified URI. I + *

    NOTE: unknown properties are always returned as null + * + * @param name The name of property to be retrieved. + * @return The value of named property. + */ + Object getProperty(String name); + + /** + * Set to use binary output stream with given encoding. + */ + void setOutput(OutputStream os, String encoding) + throws IOException, IllegalArgumentException, IllegalStateException; + + /** + * Set the output to the given writer. + *

    WARNING no information about encoding is available! + */ + void setOutput(Writer writer) + throws IOException, IllegalArgumentException, IllegalStateException; + + /** + * Write <?xml declaration with encoding (if encoding not null) + * and standalone flag (if standalone not null) + * This method can only be called just after setOutput. + */ + void startDocument(String encoding, Boolean standalone) + throws IOException, IllegalArgumentException, IllegalStateException; + + /** + * Finish writing. All unclosed start tags will be closed and output + * will be flushed. After calling this method no more output can be + * serialized until next call to setOutput() + */ + void endDocument() + throws IOException, IllegalArgumentException, IllegalStateException; + + /** + * Binds the given prefix to the given namespace. + * This call is valid for the next element including child elements. + * The prefix and namespace MUST be always declared even if prefix + * is not used in element (startTag() or attribute()) - for XML 1.0 + * it must result in declaring xmlns:prefix='namespace' + * (or xmlns:prefix="namespace" depending what character is used + * to quote attribute value). + * + *

    NOTE: this method MUST be called directly before startTag() + * and if anything but startTag() or setPrefix() is called next there will be exception. + *

    NOTE: prefixes "xml" and "xmlns" are already bound + * and can not be redefined see: + * Namespaces in XML Errata. + *

    NOTE: to set default namespace use as prefix empty string. + * + * @param prefix must be not null (or IllegalArgumentException is thrown) + * @param namespace must be not null + */ + void setPrefix(String prefix, String namespace) + throws IOException, IllegalArgumentException, IllegalStateException; + + /** + * Return namespace that corresponds to given prefix + * If there is no prefix bound to this namespace return null + * but if generatePrefix is false then return generated prefix. + * + *

    NOTE: if the prefix is empty string "" and defualt namespace is bound + * to this prefix then empty string ("") is returned. + * + *

    NOTE: prefixes "xml" and "xmlns" are already bound + * will have values as defined + * Namespaces in XML specification + */ + String getPrefix(String namespace, boolean generatePrefix) + throws IllegalArgumentException; + + /** + * Returns the current depth of the element. + * Outside the root element, the depth is 0. The + * depth is incremented by 1 when startTag() is called. + * The depth is decremented after the call to endTag() + * event was observed. + * + *

    +     * <!-- outside -->     0
    +     * <root>               1
    +     *   sometext                 1
    +     *     <foobar>         2
    +     *     </foobar>        2
    +     * </root>              1
    +     * <!-- outside -->     0
    +     * 
    + */ + int getDepth(); + + /** + * Returns the namespace URI of the current element as set by startTag(). + * + *

    NOTE: that measn in particaulr that:

      + *
    • if there was startTag("", ...) then getNamespace() returns "" + *
    • if there was startTag(null, ...) then getNamespace() returns null + *
    + * + * @return namespace set by startTag() that is currently in scope + */ + String getNamespace(); + + /** + * Returns the name of the current element as set by startTag(). + * It can only be null before first call to startTag() + * or when last endTag() is called to close first startTag(). + * + * @return namespace set by startTag() that is currently in scope + */ + String getName(); + + /** + * Writes a start tag with the given namespace and name. + * If there is no prefix defined for the given namespace, + * a prefix will be defined automatically. + * The explicit prefixes for namespaces can be established by calling setPrefix() + * immediately before this method. + * If namespace is null no namespace prefix is printed but just name. + * If namespace is empty string then serialzier will make sure that + * default empty namespace is declared (in XML 1.0 xmlns='') + * or throw IllegalStateException if default namespace is already bound + * to non-empty string. + */ + XmlSerializer startTag(String namespace, String name) + throws IOException, IllegalArgumentException, IllegalStateException; + + /** + * Write an attribute. Calls to attribute() MUST follow a call to + * startTag() immediately. If there is no prefix defined for the + * given namespace, a prefix will be defined automatically. + * If namespace is null or empty string + * no namespace prefix is printed but just name. + */ + XmlSerializer attribute(String namespace, String name, String value) + throws IOException, IllegalArgumentException, IllegalStateException; + + /** + * Write end tag. Repetition of namespace and name is just for avoiding errors. + *

    Background: in kXML endTag had no arguments, and non matching tags were + * very difficult to find... + * If namespace is null no namespace prefix is printed but just name. + * If namespace is empty string then serialzier will make sure that + * default empty namespace is declared (in XML 1.0 xmlns=''). + */ + XmlSerializer endTag(String namespace, String name) + throws IOException, IllegalArgumentException, IllegalStateException; + + + // /** + // * Writes a start tag with the given namespace and name. + // *
    If there is no prefix defined (prefix == null) for the given namespace, + // * a prefix will be defined automatically. + // *
    If explicit prefixes is passed (prefix != null) then it will be used + // *and namespace declared if not already declared or + // * throw IllegalStateException the same prefix was already set on this + // * element (setPrefix()) and was bound to different namespace. + // *
    If namespace is null then prefix must be null too or IllegalStateException is thrown. + // *
    If namespace is null then no namespace prefix is printed but just name. + // *
    If namespace is empty string then serializer will make sure that + // * default empty namespace is declared (in XML 1.0 xmlns='') + // * or throw IllegalStateException if default namespace is already bound + // * to non-empty string. + // */ + // XmlSerializer startTag (String prefix, String namespace, String name) + // throws IOException, IllegalArgumentException, IllegalStateException; + // + // /** + // * Write an attribute. Calls to attribute() MUST follow a call to + // * startTag() immediately. + // *
    If there is no prefix defined (prefix == null) for the given namespace, + // * a prefix will be defined automatically. + // *
    If explicit prefixes is passed (prefix != null) then it will be used + // * and namespace declared if not already declared or + // * throw IllegalStateException the same prefix was already set on this + // * element (setPrefix()) and was bound to different namespace. + // *
    If namespace is null then prefix must be null too or IllegalStateException is thrown. + // *
    If namespace is null then no namespace prefix is printed but just name. + // *
    If namespace is empty string then serializer will make sure that + // * default empty namespace is declared (in XML 1.0 xmlns='') + // * or throw IllegalStateException if default namespace is already bound + // * to non-empty string. + // */ + // XmlSerializer attribute (String prefix, String namespace, String name, String value) + // throws IOException, IllegalArgumentException, IllegalStateException; + // + // /** + // * Write end tag. Repetition of namespace, prefix, and name is just for avoiding errors. + // *
    If namespace or name arguments are different from corresponding startTag call + // * then IllegalArgumentException is thrown, if prefix argument is not null and is different + // * from corresponding starTag then IllegalArgumentException is thrown. + // *
    If namespace is null then prefix must be null too or IllegalStateException is thrown. + // *
    If namespace is null then no namespace prefix is printed but just name. + // *
    If namespace is empty string then serializer will make sure that + // * default empty namespace is declared (in XML 1.0 xmlns=''). + // *

    Background: in kXML endTag had no arguments, and non matching tags were + // * very difficult to find...

    + // */ + // ALEK: This is really optional as prefix in end tag MUST correspond to start tag but good for error checking + // XmlSerializer endTag (String prefix, String namespace, String name) + // throws IOException, IllegalArgumentException, IllegalStateException; + + /** + * Writes text, where special XML chars are escaped automatically + */ + XmlSerializer text(String text) + throws IOException, IllegalArgumentException, IllegalStateException; + + /** + * Writes text, where special XML chars are escaped automatically + */ + XmlSerializer text(char[] buf, int start, int len) + throws IOException, IllegalArgumentException, IllegalStateException; + + void cdsect(String text) + throws IOException, IllegalArgumentException, IllegalStateException; + void entityRef(String text) throws IOException, + IllegalArgumentException, IllegalStateException; + void processingInstruction(String text) + throws IOException, IllegalArgumentException, IllegalStateException; + void comment(String text) + throws IOException, IllegalArgumentException, IllegalStateException; + void docdecl(String text) + throws IOException, IllegalArgumentException, IllegalStateException; + void ignorableWhitespace(String text) + throws IOException, IllegalArgumentException, IllegalStateException; + + /** + * Write all pending output to the stream. + * If method startTag() or attribute() was called then start tag is closed (final >) + * before flush() is called on underlying output stream. + * + *

    NOTE: if there is need to close start tag + * (so no more attribute() calls are allowed) but without flushinging output + * call method text() with empty string (text("")). + * + */ + void flush() + throws IOException; + +} + diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..586f50a --- /dev/null +++ b/build.gradle @@ -0,0 +1,49 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +allprojects { + apply plugin: 'maven' + apply plugin: 'idea' + apply plugin: 'eclipse' +// version = '1.0' +} + +defaultTasks('clean','distZip') + +subprojects { + apply plugin: 'java' + apply plugin: 'maven' + sourceCompatibility = 1.7 + targetCompatibility = 1.7 + + + +// task packageSources(type: Jar) { +// classifier = 'sources' +// from sourceSets.main.allSource +// } +// artifacts.archives packageSources + + repositories { + mavenCentral() + } + + [compileJava, compileTestJava]*.options.collect {options ->options.encoding = 'UTF-8'} + + dependencies { + compile fileTree(dir: 'libs', include: '*.jar') + } + + jar { + manifest { + attributes("Implementation-Title": project.name, + "Implementation-Version": project.version, + "Build-Time": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"), + "Build-Number": System.env.BUILD_NUMBER?System.env.BUILD_NUMBER:"-1", + ) + } + from (project.parent.projectDir) { + include 'NOTICE.txt' + include 'LICENSE.txt' + into('META-INF') + } + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..c61ee20 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,19 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=false diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..f6b961f Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..bb0cdb9 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Mar 06 01:11:44 CST 2019 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.8-all.zip diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..cccdd3d --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..e95643d --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..9f545c6 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'Xpatch' +include ':axmlprinter', ':xpatch' diff --git a/xpatch/build.gradle b/xpatch/build.gradle new file mode 100644 index 0000000..fe377e0 --- /dev/null +++ b/xpatch/build.gradle @@ -0,0 +1,33 @@ +apply plugin: 'java-library' + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + compile project(':axmlprinter') +} + +jar{ + baseName = "xpatch" +// version = '1.0' + manifest { + attributes 'Main-Class': 'com.storm.wind.xpatch.MainCommand' + } + //添加将引用的jar的源码打入最终的jar + from { + (configurations.runtime).collect { + it.isDirectory() ? it : zipTree(it) + } + } + + from fileTree(dir:'src/main', includes: ['assets/**']) + + //排除引用的jar中的签名信息 + exclude 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA', 'META-INF/*.MF' +} + +//添加源码中引入的非代码文件,例如资源等 +sourceSets.main.resources { + srcDirs = [ + "src/main/java", + ]; + include "**/*.*" +} \ No newline at end of file diff --git a/xpatch/libs/dex-tools-2.1-SNAPSHOT.jar b/xpatch/libs/dex-tools-2.1-SNAPSHOT.jar new file mode 100644 index 0000000..0b780ac Binary files /dev/null and b/xpatch/libs/dex-tools-2.1-SNAPSHOT.jar differ diff --git a/xpatch/src/main/assets/classes.dex b/xpatch/src/main/assets/classes.dex new file mode 100644 index 0000000..af00093 Binary files /dev/null and b/xpatch/src/main/assets/classes.dex differ diff --git a/xpatch/src/main/assets/jarsigner b/xpatch/src/main/assets/jarsigner new file mode 100644 index 0000000..c3e80cc Binary files /dev/null and b/xpatch/src/main/assets/jarsigner differ diff --git a/xpatch/src/main/assets/keystore b/xpatch/src/main/assets/keystore new file mode 100644 index 0000000..67f1939 Binary files /dev/null and b/xpatch/src/main/assets/keystore differ diff --git a/xpatch/src/main/assets/lib/arm64-v8a/libxpatch_wl.so b/xpatch/src/main/assets/lib/arm64-v8a/libxpatch_wl.so new file mode 100644 index 0000000..8abe6bf Binary files /dev/null and b/xpatch/src/main/assets/lib/arm64-v8a/libxpatch_wl.so differ diff --git a/xpatch/src/main/assets/lib/armeabi-v7a/libxpatch_wl.so b/xpatch/src/main/assets/lib/armeabi-v7a/libxpatch_wl.so new file mode 100644 index 0000000..7c7dd96 Binary files /dev/null and b/xpatch/src/main/assets/lib/armeabi-v7a/libxpatch_wl.so differ diff --git a/xpatch/src/main/java/com/storm/wind/xpatch/MainCommand.java b/xpatch/src/main/java/com/storm/wind/xpatch/MainCommand.java new file mode 100644 index 0000000..370d77f --- /dev/null +++ b/xpatch/src/main/java/com/storm/wind/xpatch/MainCommand.java @@ -0,0 +1,188 @@ +package com.storm.wind.xpatch; + +import com.storm.wind.xpatch.base.BaseCommand; +import com.storm.wind.xpatch.task.ApkModifyTask; +import com.storm.wind.xpatch.task.BuildAndSignApkTask; +import com.storm.wind.xpatch.task.SoAndDexCopyTask; +import com.storm.wind.xpatch.util.FileUtils; +import com.storm.wind.xpatch.util.ManifestParser; + +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.regex.Pattern; + +public class MainCommand extends BaseCommand { + + private String apkPath; + + private String unzipApkFilePath; + + @Opt(opt = "o", longOpt = "output", description = "output .apk file, default is " + + "$source_apk_dir/[file-name]-xposed-signed.apk", argName = "out-apk-file") + private String output; // 输出的apk文件的目录以及名称 + + @Opt(opt = "f", longOpt = "force", hasArg = false, description = "force overwrite") + private boolean forceOverwrite = false; + + @Opt(opt = "k", longOpt = "keep", hasArg = false, description = "not delete the jar file " + + "that is changed by dex2jar and the apk zip files") + private boolean keepBuildFiles = false; + + @Opt(opt = "l", longOpt = "log", hasArg = false, description = "show all the debug logs") + private boolean showAllLogs = false; + + // 原来apk中dex文件的数量 + private int dexFileCount = 0; + + private static final String UNZIP_APK_FILE_NAME = "apk-unzip-files"; + + private static final String DEFAULT_APPLICATION_NAME = "android.app.Application"; + + private List mXpatchTasks = new ArrayList<>(); + + public static void main(String... args) { + new MainCommand().doMain(args); + } + + @Override + protected void doCommandLine() { + if (remainingArgs.length != 1) { + if (remainingArgs.length == 0) { + System.out.println("Please choose one apk file you want to process. "); + } + if (remainingArgs.length > 1) { + System.out.println("This tool can only used with one apk file."); + } + usage(); + return; + } + + apkPath = remainingArgs[0]; + + File srcApkFile = new File(apkPath); + + if (!srcApkFile.exists()) { + System.out.println(" The source apk file not exsit, please choose another one. " + + "current apk file is = " + apkPath); + return; + } + + String srcApkFileParentPath = srcApkFile.getParent(); + if (srcApkFileParentPath == null) { + String absPath = srcApkFile.getAbsolutePath(); + int index = absPath.lastIndexOf(File.separatorChar); + srcApkFileParentPath = absPath.substring(0, index); + } + + String currentDir = new File(".").getAbsolutePath(); // 当前命令行所在的目录 + if (showAllLogs) { + System.out.println(" currentDir = " + currentDir + " \n apkPath = " + apkPath); + } + + if (output == null || output.length() == 0) { + output = getBaseName(apkPath) + "-xposed-signed.apk"; + } + + File outputFile = new File(output); + if (outputFile.exists() && !forceOverwrite) { + System.err.println(output + " exists, use --force to overwrite"); + usage(); + return; + } + + System.out.println(" !!!!! output apk path --> " + output); + + String apkFileName = getBaseName(srcApkFile); + + // 中间文件临时存储的位置 + String tempFilePath = srcApkFileParentPath + File.separator + currentTimeStr() + "-tmp" + File.separator; + + // apk文件解压的目录 + unzipApkFilePath = tempFilePath + apkFileName + "-" + UNZIP_APK_FILE_NAME + File.separator; + + if (showAllLogs) { + System.out.println(" !!!!! srcApkFileParentPath = " + srcApkFileParentPath + + "\n unzipApkFilePath = " + unzipApkFilePath); + } + + // 先解压apk到指定目录下 + FileUtils.decompressZip(apkPath, unzipApkFilePath); + + // Get the dex count in the apk zip file + dexFileCount = findDexFileCount(unzipApkFilePath); + + if (showAllLogs) { + System.out.println(" --- dexFileCount = " + dexFileCount); + } + + String manifestFilePath = unzipApkFilePath + "AndroidManifest.xml"; + + // parse the app main application full name from the manifest file + ManifestParser.Pair pair = ManifestParser.parseManidestFile(manifestFilePath); + String applicationName; + if (pair != null && pair.applictionName != null) { + applicationName = pair.applictionName; + } else { + System.out.println(" Application name not found error !!!!!! "); + applicationName = DEFAULT_APPLICATION_NAME; + } + + if (showAllLogs) { + System.out.println(" Get the application name --> " + applicationName); + } + + // 1. modify the apk dex file to make xposed can run in it + mXpatchTasks.add(new ApkModifyTask(showAllLogs, keepBuildFiles, unzipApkFilePath, applicationName, + dexFileCount)); + + // 2. copy xposed so and dex files into the unzipped apk + mXpatchTasks.add(new SoAndDexCopyTask(dexFileCount, unzipApkFilePath)); + + // 3. compress all files into an apk and then sign it. + mXpatchTasks.add(new BuildAndSignApkTask(keepBuildFiles, unzipApkFilePath, output)); + + // 4. excute these tasks + for (Runnable executor : mXpatchTasks) { + executor.run(); + } + + // 5. delete all the build files that is useless now + File unzipApkFile = new File(unzipApkFilePath); + if (!keepBuildFiles && unzipApkFile.exists()) { + FileUtils.deleteDir(unzipApkFile); + } + + File tempFile = new File(tempFilePath); + if (!keepBuildFiles && tempFile.exists()) { + tempFile.delete(); + } + } + + private int findDexFileCount(String unzipApkFilePath) { + File zipfileRoot = new File(unzipApkFilePath); + if (!zipfileRoot.exists()) { + return 0; + } + File[] childFiles = zipfileRoot.listFiles(); + if (childFiles == null || childFiles.length == 0) { + return 0; + } + int count = 0; + for (File file : childFiles) { + String fileName = file.getName(); + if (Pattern.matches("classes.*\\.dex", fileName)) { + count++; + } + } + return count; + } + + // Use the current timestamp as the name of the build file + private String currentTimeStr() { + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");//设置日期格式 + return df.format(new Date()); + } +} diff --git a/xpatch/src/main/java/com/storm/wind/xpatch/base/BaseCommand.java b/xpatch/src/main/java/com/storm/wind/xpatch/base/BaseCommand.java new file mode 100644 index 0000000..f804536 --- /dev/null +++ b/xpatch/src/main/java/com/storm/wind/xpatch/base/BaseCommand.java @@ -0,0 +1,460 @@ +package com.storm.wind.xpatch.base; + +import java.io.File; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +/** + * Created by Wind + */ +public abstract class BaseCommand { + + private String onlineHelp; + + protected Map optMap = new HashMap(); + + @Opt(opt = "h", longOpt = "help", hasArg = false, description = "Print this help message") + private boolean printHelp = false; + + protected String remainingArgs[]; + protected String orginalArgs[]; + + @Retention(value = RetentionPolicy.RUNTIME) + @Target(value = { ElementType.FIELD }) + static public @interface Opt { + String argName() default ""; + + String description() default ""; + + boolean hasArg() default true; + + String longOpt() default ""; + + String opt() default ""; + + boolean required() default false; + } + + static protected class Option implements Comparable