diff --git a/src/main/java/com/reandroid/xml/ElementWriter.java b/src/main/java/com/reandroid/xml/ElementWriter.java new file mode 100755 index 0000000..8676f9b --- /dev/null +++ b/src/main/java/com/reandroid/xml/ElementWriter.java @@ -0,0 +1,79 @@ + /* + * Copyright (C) 2022 github.com/REAndroid + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.reandroid.xml; + +import java.io.IOException; +import java.io.Writer; + +class ElementWriter extends Writer { + private final Writer mWriter; + private final long mMaxLen; + private final boolean mUnlimitedLength; + private long mCurrentLength; + private boolean mLengthFinished; + ElementWriter(Writer writer, long maxLen){ + mWriter=writer; + this.mMaxLen=maxLen; + this.mUnlimitedLength=maxLen<0; + } + ElementWriter(Writer writer){ + this(writer, -1); + } + boolean isFinished(){ + return mLengthFinished; + } + private boolean mInterruptedWritten; + void writeInterrupted(){ + if(!mLengthFinished){ + return; + } + if(mInterruptedWritten){ + return; + } + mInterruptedWritten=true; + String txt="\n .\n .\n .\n more items ...\n"; + try { + mWriter.write(txt); + } catch (IOException e) { + } + } + @Override + public void write(char[] chars, int i, int i1) throws IOException { + updateCurrentLength(i1); + mWriter.write(chars, i, i1); + } + + @Override + public void flush() throws IOException { + mWriter.flush(); + } + @Override + public void close() throws IOException { + mWriter.close(); + } + private boolean updateCurrentLength(int len){ + if(mUnlimitedLength){ + return false; + } + if(mLengthFinished){ + mLengthFinished=true; + //return true; + } + mCurrentLength+=len; + mLengthFinished=mCurrentLength>=mMaxLen; + return mLengthFinished; + } +} diff --git a/src/main/java/com/reandroid/xml/NameSpaceItem.java b/src/main/java/com/reandroid/xml/NameSpaceItem.java new file mode 100755 index 0000000..392dd8f --- /dev/null +++ b/src/main/java/com/reandroid/xml/NameSpaceItem.java @@ -0,0 +1,156 @@ + /* + * Copyright (C) 2022 github.com/REAndroid + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.reandroid.xml; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class NameSpaceItem { + private String prefix; + private String namespaceUri; + public NameSpaceItem(String prefix, String nsUri){ + this.prefix=prefix; + this.namespaceUri=nsUri; + validate(); + } + public String toAttributeName(){ + return ATTR_PREFIX+":"+prefix; + } + public SchemaAttr toSchemaAttribute(){ + return new SchemaAttr(getPrefix(), getNamespaceUri()); + } + public boolean isPrefixEqual(String p){ + if(XMLUtil.isEmpty(prefix)){ + return false; + } + return prefix.equals(p); + } + public boolean isUriEqual(String nsUri){ + if(XMLUtil.isEmpty(namespaceUri)){ + return false; + } + return namespaceUri.equals(nsUri); + } + public boolean isValid(){ + return isPrefixValid() && isUriValid(); + } + private boolean validate(){ + boolean preOk=isPrefixValid(); + boolean uriOk=isUriValid(); + if(preOk && uriOk){ + if(!NAME_ANDROID.equals(prefix) && URI_ANDROID.equals(namespaceUri)){ + namespaceUri= URI_APP; + } + return true; + } + if(!preOk && !uriOk){ + return false; + } + if(!preOk){ + if(URI_ANDROID.equals(namespaceUri)){ + prefix= NAME_ANDROID; + }else { + prefix= NAME_APP; + } + } + if(!uriOk){ + if(NAME_ANDROID.equals(prefix)){ + namespaceUri= URI_ANDROID; + }else { + namespaceUri= URI_APP; + } + } + return true; + } + private boolean isPrefixValid(){ + return !XMLUtil.isEmpty(prefix); + } + private boolean isUriValid(){ + if(XMLUtil.isEmpty(namespaceUri)){ + return false; + } + Matcher matcher=PATTERN_URI.matcher(namespaceUri); + return matcher.find(); + } + public String getNamespaceUri() { + return namespaceUri; + } + public String getPrefix() { + return prefix; + } + public void setNamespaceUri(String namespaceUri) { + this.namespaceUri = namespaceUri; + validate(); + } + public void setPrefix(String prefix) { + this.prefix = prefix; + validate(); + } + @Override + public boolean equals(Object o){ + if(o instanceof NameSpaceItem){ + return isUriEqual(((NameSpaceItem)o).namespaceUri); + } + return false; + } + @Override + public int hashCode(){ + String u=namespaceUri; + if(u==null){ + u=""; + } + return u.hashCode(); + } + @Override + public String toString(){ + StringBuilder builder=new StringBuilder(); + boolean appendOnce=false; + if(namespaceUri!=null){ + builder.append(namespaceUri); + appendOnce=true; + } + if(prefix!=null){ + if(appendOnce){ + builder.append(':'); + } + builder.append(prefix); + } + return builder.toString(); + } + private static NameSpaceItem ns_android; + private static NameSpaceItem ns_app; + public static NameSpaceItem getAndroid(){ + if(ns_android==null){ + ns_android=new NameSpaceItem(NAME_ANDROID, URI_ANDROID); + } + return ns_android; + } + public static NameSpaceItem getApp(){ + if(ns_app==null){ + ns_app=new NameSpaceItem(NAME_APP, URI_APP); + } + return ns_app; + } + private static final Pattern PATTERN_URI=Pattern.compile("^https?://[^\\s/]+/[^\\s]+$"); + + + private static final String ATTR_PREFIX = "xmlns"; + private static final String URI_ANDROID = "http://schemas.android.com/apk/res/android"; + private static final String URI_APP = "http://schemas.android.com/apk/res-auto"; + private static final String NAME_ANDROID = "android"; + private static final String NAME_APP = "app"; + +} diff --git a/src/main/java/com/reandroid/xml/SchemaAttr.java b/src/main/java/com/reandroid/xml/SchemaAttr.java new file mode 100755 index 0000000..3c850a9 --- /dev/null +++ b/src/main/java/com/reandroid/xml/SchemaAttr.java @@ -0,0 +1,117 @@ + /* + * Copyright (C) 2022 github.com/REAndroid + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.reandroid.xml; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SchemaAttr extends XMLAttribute { + private static final String DEFAULT_XMLNS="xmlns"; + private String mXmlns; + private String mPrefix; + public SchemaAttr(String prefix, String uri) { + this(DEFAULT_XMLNS, prefix, uri); + } + public SchemaAttr(String xmlns, String prefix, String uri) { + super(prefix, uri); + this.set(xmlns, prefix, uri); + } + private void set(String xmlns, String prefix, String uri){ + setXmlns(xmlns); + if(XMLUtil.isEmpty(prefix)){ + prefix=null; + } + setName(prefix); + setUri(uri); + } + @Override + public void setName(String fullName){ + if(fullName==null){ + setPrefix(null); + return; + } + int i=fullName.indexOf(':'); + if(i>0 && ihttps?://[^:\\s]+)(:(?([^:/\\s]+)))?\\s*$"); +} diff --git a/src/main/java/com/reandroid/xml/XMLAttribute.java b/src/main/java/com/reandroid/xml/XMLAttribute.java new file mode 100755 index 0000000..27a56f1 --- /dev/null +++ b/src/main/java/com/reandroid/xml/XMLAttribute.java @@ -0,0 +1,158 @@ + /* + * Copyright (C) 2022 github.com/REAndroid + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.reandroid.xml; + + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; + +public class XMLAttribute { + private int mNameId; + private int mValueId; + private Object mValueTag; + private Object mNameTag; + private String mName; + private String mValue; + public XMLAttribute(String name, String val){ + mName=name; + mValue= XMLUtil.escapeXmlChars(val); + } + public void setNameId(String id){ + setNameId(XMLUtil.hexToInt(id,0)); + } + public void setNameId(int id){ + mNameId=id; + } + public void setValueId(String id){ + setValueId(XMLUtil.hexToInt(id,0)); + } + public void setValueId(int id){ + mValueId=id; + } + public int getNameId(){ + return mNameId; + } + public int getValueId(){ + return mValueId; + } + public XMLAttribute cloneAttr(){ + XMLAttribute baseAttr=new XMLAttribute(getName(),getValue()); + baseAttr.setNameId(getNameId()); + baseAttr.setValueId(getValueId()); + return baseAttr; + } + public Object getValueTag(){ + return mValueTag; + } + public void setValueTag(Object obj){ + mValueTag =obj; + } + public Object geNameTag(){ + return mNameTag; + } + public void setNameTag(Object obj){ + mNameTag =obj; + } + public String getName(){ + return mName; + } + public String getNamePrefix(){ + int i=mName.indexOf(":"); + if(i>0){ + return mName.substring(0,i); + } + return null; + } + public String getNameWoPrefix(){ + int i=mName.indexOf(":"); + if(i>0){ + return mName.substring(i+1); + } + return mName; + } + public String getValue(){ + if(mValue==null){ + mValue=""; + } + return mValue; + } + public int getValueInt(){ + long l=Long.decode(getValue()); + return (int)l; + } + public boolean getValueBool(){ + String str=getValue().toLowerCase(); + if("true".equals(str)){ + return true; + } + return false; + } + public boolean isValueBool(){ + String str=getValue().toLowerCase(); + if("true".equals(str)){ + return true; + } + return "false".equals(str); + } + public void setName(String name){ + mName=name; + } + public void setValue(String val){ + mValue= XMLUtil.escapeXmlChars(val); + } + public boolean isEmpty(){ + return XMLUtil.isEmpty(getName()); + } + @Override + public boolean equals(Object obj){ + if(obj instanceof XMLAttribute){ + XMLAttribute attr=(XMLAttribute)obj; + if(isEmpty()){ + return attr.isEmpty(); + } + String s=toString(); + return s.equals(attr.toString()); + } + return false; + } + public boolean write(Writer writer) throws IOException { + if(isEmpty()){ + return false; + } + writer.write(getName()); + writer.write("=\""); + String val= XMLUtil.trimQuote(getValue()); + val= XMLUtil.escapeXmlChars(val); + val= XMLUtil.escapeQuote(val); + writer.write(val); + writer.write('"'); + return true; + } + @Override + public String toString(){ + if(isEmpty()){ + return null; + } + StringWriter writer=new StringWriter(); + try { + write(writer); + } catch (IOException e) { + } + writer.flush(); + return writer.toString(); + } +} diff --git a/src/main/java/com/reandroid/xml/XMLComment.java b/src/main/java/com/reandroid/xml/XMLComment.java new file mode 100755 index 0000000..d71aa3a --- /dev/null +++ b/src/main/java/com/reandroid/xml/XMLComment.java @@ -0,0 +1,91 @@ + /* + * Copyright (C) 2022 github.com/REAndroid + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.reandroid.xml; + + +import java.io.IOException; +import java.io.Writer; + +public class XMLComment extends XMLElement { + private String mStart; + private String mEnd; + private boolean mIsHidden; + public XMLComment(String commentText){ + this(); + setCommentText(commentText); + } + public XMLComment(){ + super(); + initializeStartEnd(); + } + @Override + XMLElement onCloneElement(){ + XMLComment ce=new XMLComment(getCommentText()); + ce.setHidden(isHidden()); + return ce; + } + @Override + void cloneAllAttributes(XMLElement element){ + } + public void setHidden(boolean hide){ + mIsHidden=hide; + } + public boolean isHidden(){ + return mIsHidden; + } + public void setCommentText(String text){ + setTextContent(text); + } + public String getCommentText(){ + return getTextContent(); + } + private void initializeStartEnd(){ + setTagName(""); + mStart=""; + setStart(mStart); + setEnd(mEnd); + setStartPrefix(""); + setEndPrefix(""); + } + @Override + int getChildIndent(){ + return getIndent(); + } + @Override + boolean isEmpty(){ + return XMLUtil.isEmpty(getTextContent()); + } + + @Override + public boolean write(Writer writer, boolean newLineAttributes) throws IOException { + if(isHidden()){ + return false; + } + if(isEmpty()){ + return false; + } + boolean appendOnce=appendComments(writer); + if(appendOnce){ + writer.write(XMLUtil.NEW_LINE); + } + appendIndentText(writer); + writer.write(mStart); + writer.write(getCommentText()); + writer.write(mEnd); + return true; + } +} diff --git a/src/main/java/com/reandroid/xml/XMLDocument.java b/src/main/java/com/reandroid/xml/XMLDocument.java new file mode 100755 index 0000000..b75b1c3 --- /dev/null +++ b/src/main/java/com/reandroid/xml/XMLDocument.java @@ -0,0 +1,261 @@ + /* + * Copyright (C) 2022 github.com/REAndroid + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.reandroid.xml; + +import com.reandroid.xml.parser.XMLDocumentParser; +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.Comparator; + +public class XMLDocument { + private XMLElement mDocumentElement; + private Object mTag; + private String mName; + private String mConfigName; + private float mIndentScale; + private XmlHeaderElement mHeaderElement; + private Object mLastElementSorter; + public XMLDocument(String elementName){ + this(); + XMLElement docElem=new XMLElement(elementName); + setDocumentElement(docElem); + } + public XMLDocument(){ + mIndentScale=0.5f; + mHeaderElement = new XmlHeaderElement(); + } + public void setHeaderElement(XmlHeaderElement headerElement){ + this.mHeaderElement=headerElement; + } + public void hideComments(boolean hide){ + hideComments(true, hide); + } + public void hideComments(boolean recursive, boolean hide){ + if(mDocumentElement==null){ + return; + } + mDocumentElement.hideComments(recursive, hide); + } + public XmlHeaderElement getHeaderElement(){ + return mHeaderElement; + } + + public void sortDocumentElement(Comparator comparator){ + if(mDocumentElement==null||comparator==null){ + return; + } + if(mLastElementSorter !=null){ + if(mLastElementSorter.getClass().equals(comparator.getClass())){ + return; + } + } + mLastElementSorter=comparator; + mDocumentElement.sortChildes(comparator); + } + public void setIndentScalePercent(int val){ + int percent; + if(val>100){ + percent=100; + }else if(val<0){ + percent=0; + }else { + percent=val; + } + mIndentScale=percent/100.0f; + XMLElement docElem=getDocumentElement(); + if(docElem!=null){ + docElem.setIndentScale(mIndentScale); + } + } + public String getName(){ + return mName; + } + public String getConfigName(){ + return mConfigName; + } + public void setName(String name){ + mName=name; + } + public void setConfigName(String configName){ + mConfigName=configName; + } + public Object getTag(){ + return mTag; + } + public void setTag(Object obj){ + mTag=obj; + } + public XMLElement createElement(String tag) { + XMLElement docEl=getDocumentElement(); + if(docEl==null){ + docEl=new XMLElement(tag); + setDocumentElement(docEl); + return docEl; + } + XMLElement baseElement=docEl.createElement(tag); + return baseElement; + } + public XMLElement getDocumentElement(){ + return mDocumentElement; + } + public void setDocumentElement(XMLElement baseElement){ + mDocumentElement=baseElement; + if(baseElement!=null){ + baseElement.setIndentScale(mIndentScale); + } + } + private String getElementString(boolean newLineAttributes){ + XMLElement baseElement=getDocumentElement(); + if(baseElement==null){ + return null; + } + return baseElement.toString(); + } + private boolean appendDocumentElement(Writer writer, boolean newLineAttributes) throws IOException { + if(mDocumentElement==null){ + return false; + } + return mDocumentElement.write(writer, newLineAttributes); + } + private boolean appendDocumentAttribute(Writer writer) throws IOException { + XmlHeaderElement headerElement=getHeaderElement(); + if(headerElement==null){ + return false; + } + return headerElement.write(writer, false); + } + public boolean saveAndroidResource(File file) throws IOException{ + if(file==null){ + throw new IOException("File is null"); + } + File dir=file.getParentFile(); + if(!dir.exists()){ + dir.mkdirs(); + } + FileOutputStream out=new FileOutputStream(file,false); + return saveAndroidResource(out); + } + public boolean saveAndroidValuesResource(File file) throws IOException{ + if(file==null){ + throw new IOException("File is null"); + } + File dir=file.getParentFile(); + if(!dir.exists()){ + dir.mkdirs(); + } + FileOutputStream out=new FileOutputStream(file,false); + return saveAndroidValuesResource(out); + } + public boolean saveAndroidResource(OutputStream out) throws IOException{ + setIndent(1); + hideComments(true); + return save(out, true); + } + public boolean saveAndroidValuesResource(OutputStream out) throws IOException{ + setIndent(1); + //hideComments(true); + return save(out, false); + } + + public boolean save(OutputStream out, boolean newLineAttributes) throws IOException{ + OutputStreamWriter writer=new OutputStreamWriter(out, StandardCharsets.UTF_8); + boolean result= write(writer, newLineAttributes); + writer.flush(); + writer.close(); + return result; + } + public boolean save(File file, boolean newLineAttributes) throws IOException{ + File dir=file.getParentFile(); + if(dir!=null&&!dir.exists()){ + dir.mkdirs(); + } + setIndent(1); + FileWriter writer=new FileWriter(file,false); + boolean result= write(writer, newLineAttributes); + writer.flush(); + writer.close(); + return result; + } + public boolean write(Writer writer, boolean newLineAttributes) throws IOException{ + boolean has_header=appendDocumentAttribute(writer); + if(has_header){ + writer.write(XMLUtil.NEW_LINE); + } + return appendDocumentElement(writer, newLineAttributes); + } + public String toText(){ + return toText(1, false); + } + public String toText(boolean newLineAttributes){ + return toText(1, newLineAttributes); + } + public String toText(int indent, boolean newLineAttributes){ + StringWriter writer=new StringWriter(); + setIndent(indent); + try { + write(writer, newLineAttributes); + writer.flush(); + writer.close(); + } catch (IOException ignored) { + } + return writer.toString(); + } + + @Override + public String toString(){ + StringWriter strWriter=new StringWriter(); + ElementWriter writer=new ElementWriter(strWriter, XMLElement.DEBUG_TO_STRING); + try { + write(writer, false); + } catch (IOException e) { + } + strWriter.flush(); + return strWriter.toString(); + } + public static XMLDocument load(String text) throws XMLException { + XMLDocumentParser parser=new XMLDocumentParser(text); + return parser.parse(); + } + public static XMLDocument load(InputStream in) throws XMLException { + if(in==null){ + throw new XMLException("InputStream=null"); + } + XMLDocumentParser parser=new XMLDocumentParser(in); + return parser.parse(); + } + public static XMLDocument load(File file) throws XMLException { + XMLDocumentParser parser=new XMLDocumentParser(file); + XMLDocument resDocument=parser.parse(); + if(resDocument!=null){ + if(resDocument.getTag()==null){ + resDocument.setTag(file); + } + } + return resDocument; + } + + public void setIndent(int indent){ + XMLElement docEle=getDocumentElement(); + if(docEle==null){ + return; + } + docEle.setIndent(indent); + } + public static String htmlToXml(String htmlString){ + return XMLUtil.htmlToXml(htmlString); + } + +} diff --git a/src/main/java/com/reandroid/xml/XMLElement.java b/src/main/java/com/reandroid/xml/XMLElement.java new file mode 100755 index 0000000..a5b02c6 --- /dev/null +++ b/src/main/java/com/reandroid/xml/XMLElement.java @@ -0,0 +1,1054 @@ + /* + * Copyright (C) 2022 github.com/REAndroid + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.reandroid.xml; + + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.util.*; + +public class XMLElement { + static final long DEBUG_TO_STRING=500; + private String mTagName; + private XMLTextAttribute mTextAttribute; + private List mAttributes; + private List mChildes; + private List mComments; + private XMLElement mParent; + private int mIndent; + private Object mTag; + private int mResId; + private float mIndentScale; + private String mUniqueAttrName; + private List mUniqueNameValues; + private String mStart; + private String mStartPrefix; + private String mEnd; + private String mEndPrefix; + private Set nameSpaceItems; + public XMLElement(String tagName){ + this(); + setTagName(tagName); + } + public XMLElement(){ + setDefaultStartEnd(); + } + + public String getTagNamePrefix(){ + int i=mTagName.indexOf(":"); + if(i>0){ + return mTagName.substring(0,i); + } + return null; + } + public String getTagNameWoPrefix(){ + int i=mTagName.indexOf(":"); + if(i>0){ + return mTagName.substring(i+1); + } + return mTagName; + } + private void setDefaultStartEnd(){ + this.mStart="<"; + this.mEnd=">"; + this.mStartPrefix="/"; + this.mEndPrefix="/"; + } + public XMLElement removeChild(XMLElement element){ + if(mChildes==null || element==null){ + return null; + } + int i=mChildes.indexOf(element); + if(i<0){ + return null; + } + XMLElement result=mChildes.get(i); + mChildes.remove(i); + result.setParent(null); + return result; + } + public XMLElement getFirstChildWithAttrName(String name){ + if(mChildes==null || name==null){ + return null; + } + for(XMLElement element:mChildes){ + XMLAttribute attr=element.getAttribute(name); + if(attr!=null){ + return element; + } + } + return null; + } + public XMLElement getFirstChildWithAttr(String attrName, String attrValue){ + if(mChildes==null || attrName==null || attrValue==null){ + return null; + } + for(XMLElement element:mChildes){ + XMLAttribute attr=element.getAttribute(attrName); + if(attr!=null){ + if(attrValue.equals(attr.getValue())){ + return element; + } + } + } + return null; + } + public void applyNameSpaceItems(){ + if(nameSpaceItems!=null){ + for(NameSpaceItem nsItem:nameSpaceItems){ + SchemaAttr schemaAttr=nsItem.toSchemaAttribute(); + XMLAttribute exist=getAttribute(schemaAttr.getName()); + if(exist!=null){ + exist.setValue(schemaAttr.getValue()); + }else { + addAttributeNoCheck(schemaAttr); + } + } + } + if(mParent!=null){ + mParent.applyNameSpaceItems(); + } + } + public void addNameSpace(NameSpaceItem nsItem){ + if(nsItem==null){ + return; + } + if(mParent!=null){ + mParent.addNameSpace(nsItem); + return; + } + if(nameSpaceItems==null){ + nameSpaceItems=new HashSet<>(); + } + nameSpaceItems.add(nsItem); + } + public NameSpaceItem getNameSpaceItemForUri(String uri){ + if(nameSpaceItems!=null){ + for(NameSpaceItem ns:nameSpaceItems){ + if(ns.isUriEqual(uri)){ + return ns; + } + } + } + if(mParent!=null){ + return mParent.getNameSpaceItemForUri(uri); + } + return null; + } + public NameSpaceItem getNameSpaceItemForPrefix(String prefix){ + if(nameSpaceItems!=null){ + for(NameSpaceItem ns:nameSpaceItems){ + if(ns.isPrefixEqual(prefix)){ + return ns; + } + } + } + if(mParent!=null){ + return mParent.getNameSpaceItemForPrefix(prefix); + } + return null; + } + public void setStart(String start) { + this.mStart = start; + } + public void setEnd(String end) { + this.mEnd = end; + } + public void setStartPrefix(String pfx) { + if(pfx==null){ + pfx=""; + } + this.mStartPrefix = pfx; + } + public void setEndPrefix(String pfx) { + if(pfx==null){ + pfx=""; + } + this.mEndPrefix = pfx; + } + + public void setUniqueAttrName(String attrName){ + clearUniqueNameValues(); + if(XMLUtil.isEmpty(attrName)){ + mUniqueAttrName=null; + return; + } + mUniqueAttrName=attrName; + } + public String getUniqueAttrName(){ + return mUniqueAttrName; + } + private void clearUniqueNameValues(){ + if(mUniqueNameValues!=null){ + mUniqueNameValues.clear(); + mUniqueNameValues=null; + } + } + private void addUniqueNameValues(XMLElement element){ + if(element==null){ + return; + } + XMLAttribute baseAttr=element.getAttribute(getUniqueAttrName()); + if(baseAttr==null){ + return; + } + addUniqueNameValues(baseAttr.getValue()); + } + private void addUniqueNameValues(String nameVal){ + if(XMLUtil.isEmpty(nameVal)){ + return; + } + if(mUniqueNameValues==null){ + mUniqueNameValues=new ArrayList<>(); + } + mUniqueNameValues.add(nameVal); + } + private boolean shouldCheckUniqueElement(){ + return mUniqueAttrName!=null; + } + private boolean containsUniqueNameValue(XMLElement element){ + if(element==null){ + return false; + } + return containsUniqueNameValue(element.getAttribute(getUniqueAttrName())); + } + private boolean containsUniqueNameValue(XMLAttribute baseAttr){ + if(baseAttr==null){ + return false; + } + return containsUniqueNameValue(baseAttr.getValue()); + } + private boolean containsUniqueNameValue(String nameVal){ + if(mUniqueNameValues==null|| XMLUtil.isEmpty(nameVal)){ + return false; + } + return mUniqueNameValues.contains(nameVal); + } + void setIndentScale(float scale){ + mIndentScale=scale; + } + private float getIndentScale(){ + XMLElement parent=getParent(); + if(parent==null){ + return mIndentScale; + } + return parent.getIndentScale(); + } + public int getResourceId(){ + return mResId; + } + public void setResourceId(int id){ + mResId=id; + } + public boolean containsSameChild(XMLElement baseElement){ + if(baseElement==null||mChildes==null){ + return false; + } + for(XMLElement ch:mChildes){ + if(baseElement.isSame(ch)){ + return true; + } + } + return false; + } + private String getUniqueId(){ + StringBuilder builder=new StringBuilder(getTagName()); + builder.append(attributesToString(false)); + return builder.toString(); + } + private boolean isSame(XMLElement baseElement){ + if(baseElement==null){ + return false; + } + String s1=getUniqueId(); + String s2=baseElement.getUniqueId(); + return XMLUtil.isStringEqual(s1,s2); + } + public XMLElement cloneElement(){ + XMLElement baseElement=onCloneElement(); + baseElement.setTag(getTag()); + cloneAllAttributes(baseElement); + if(mChildes!=null){ + for(XMLElement element:mChildes){ + baseElement.addChildNoCheck(element.cloneElement()); + } + } + if(mComments!=null){ + for(XMLComment ce:mComments){ + baseElement.addComment((XMLComment) ce.cloneElement()); + } + } + return baseElement; + } + void cloneAllAttributes(XMLElement element){ + if(mAttributes!=null){ + for(XMLAttribute attr:mAttributes){ + element.addAttributeNoCheck(attr.cloneAttr()); + } + } + XMLTextAttribute textAttribute=mTextAttribute; + if(textAttribute!=null){ + element.mTextAttribute=mTextAttribute.cloneAttr(); + } + } + XMLElement onCloneElement(){ + return new XMLElement(getTagName()); + } + public XMLElement createElement(String tag) { + XMLElement baseElement=new XMLElement(tag); + addChildNoCheck(baseElement); + return baseElement; + } + public boolean containsChild(XMLElement baseElement){ + if(baseElement==null||mChildes==null){ + return false; + } + return mChildes.contains(baseElement); + } + + public void addChild(XMLElement[] elements) { + if(elements==null){ + return; + } + int max=elements.length; + for(int i=0;i elements) { + if(elements==null){ + return; + } + for(XMLElement element:elements){ + addChild(element); + } + } + public void addChild(XMLElement baseElement) { + addChildNoCheck(baseElement); + } + public XMLComment getCommentAt(int index){ + if(mComments==null || index<0){ + return null; + } + if(index>=mComments.size()){ + return null; + } + return mComments.get(index); + } + public void hideComments(boolean recursive, boolean hide){ + hideComments(hide); + if(recursive && mChildes!=null){ + for(XMLElement child:mChildes){ + child.hideComments(recursive, hide); + } + } + } + private void hideComments(boolean hide){ + if(mComments==null){ + return; + } + for(XMLComment ce:mComments){ + ce.setHidden(hide); + } + } + public int getCommentsCount(){ + if(mComments==null){ + return 0; + } + return mComments.size(); + } + public void addComments(Collection commentElements){ + if(commentElements==null){ + return; + } + for(XMLComment ce:commentElements){ + addComment(ce); + } + } + public void clearComments(){ + if(mComments==null){ + return; + } + mComments.clear(); + mComments=null; + } + public void addComment(XMLComment commentElement) { + if(commentElement==null){ + return; + } + if(mComments==null){ + mComments=new ArrayList<>(); + } + mComments.add(commentElement); + commentElement.setIndent(getIndent()); + commentElement.setParent(this); + } + public void removeAllChildes(){ + if(mChildes==null){ + return; + } + mChildes.clear(); + } + public int getChildesCount(){ + if(mChildes==null){ + return 0; + } + return mChildes.size(); + } + public XMLElement getFirstElementByTagName(String tagName){ + if(tagName==null||mChildes==null){ + return null; + } + for(XMLElement element:mChildes){ + if(tagName.equals(element.getTagName())){ + return element; + } + } + return null; + } + public XMLElement[] getElementsByTagName(String tagName){ + if(tagName==null||mChildes==null){ + return null; + } + List results=new ArrayList<>(); + for(XMLElement element:mChildes){ + if(tagName.equals(element.getTagName())){ + results.add(element); + } + } + int max=results.size(); + if(max==0){ + return null; + } + return results.toArray(new XMLElement[max]); + } + public XMLElement[] getAllChildes(){ + if(mChildes==null){ + return null; + } + int max=mChildes.size(); + if(max==0){ + return null; + } + return mChildes.toArray(new XMLElement[max]); + } + public XMLElement getChildAt(int index){ + if(mChildes==null||index<0){ + return null; + } + if(index>=mChildes.size()){ + return null; + } + return mChildes.get(index); + } + public int getAttributeCount(){ + if(mAttributes==null){ + return 0; + } + return mAttributes.size(); + } + public XMLAttribute getAttributeAt(int index){ + if(mAttributes==null||index<0){ + return null; + } + if(index>=mAttributes.size()){ + return null; + } + return mAttributes.get(index); + } + public String getAttributeValue(String name){ + XMLAttribute attr=getAttribute(name); + if (attr==null){ + return null; + } + return attr.getValue(); + } + public int getAttributeValueInt(String name, int def){ + XMLAttribute attr=getAttribute(name); + if (attr==null){ + return def; + } + return attr.getValueInt(); + } + public int getAttributeValueInt(String name) throws XMLException { + XMLAttribute attr=getAttribute(name); + if (attr==null){ + throw new XMLException("Expecting integer for attr <"+name+ "> at '"+toString()+"'"); + } + try{ + return attr.getValueInt(); + }catch (NumberFormatException ex){ + throw new XMLException(ex.getMessage()+": "+" '"+toString()+"'"); + } + } + public boolean getAttributeValueBool(String name, boolean def){ + XMLAttribute attr=getAttribute(name); + if (attr==null){ + return def; + } + if(!attr.isValueBool()){ + return def; + } + return attr.getValueBool(); + } + public boolean getAttributeValueBool(String name) throws XMLException { + XMLAttribute attr=getAttribute(name); + if (attr==null || !attr.isValueBool()){ + throw new XMLException("Expecting boolean for attr <"+name+ "> at '"+toString()+"'"); + } + return attr.getValueBool(); + } + public XMLAttribute getAttribute(String name){ + if(mAttributes==null){ + return null; + } + if(XMLUtil.isEmpty(name)){ + return null; + } + for(XMLAttribute attr:mAttributes){ + if(name.equals(attr.getName())){ + return attr; + } + } + return null; + } + public XMLAttribute removeAttribute(String name){ + if(XMLUtil.isEmpty(name)){ + return null; + } + XMLAttribute attr=getAttribute(name); + if(attr==null){ + return null; + } + int i=mAttributes.indexOf(attr); + if(i<0){ + return null; + } + mAttributes.remove(i); + return attr; + } + public XMLAttribute setAttribute(String name, int value){ + return setAttribute(name, String.valueOf(value)); + } + public XMLAttribute setAttribute(String name, boolean value){ + String v=value?"true":"false"; + return setAttribute(name, v); + } + public XMLAttribute setAttribute(String name, String value){ + if(XMLUtil.isEmpty(name)){ + return null; + } + XMLAttribute attr=getAttribute(name); + if(attr==null){ + if(SchemaAttr.looksSchema(name, value)){ + attr=new SchemaAttr(name, value); + }else{ + attr=new XMLAttribute(name,value); + } + addAttributeNoCheck(attr); + }else { + attr.setValue(value); + } + return attr; + } + public void addAttributes(Collection attrs){ + if(attrs==null){ + return; + } + for(XMLAttribute a:attrs){ + addAttribute(a); + } + } + public void addAttribute(XMLAttribute attr){ + if(attr==null){ + return; + } + if(XMLUtil.isEmpty(attr.getName())){ + return; + } + XMLAttribute exist=getAttribute(attr.getName()); + if(exist!=null){ + return; + } + if(mAttributes==null){ + mAttributes=new ArrayList<>(); + } + mAttributes.add(attr); + } + private void addAttributeNoCheck(XMLAttribute attr){ + if(attr==null){ + return; + } + if(XMLUtil.isEmpty(attr.getName())){ + return; + } + if(mAttributes==null){ + mAttributes=new ArrayList<>(); + } + mAttributes.add(attr); + } + public void sortChildes(Comparator comparator){ + if(mChildes==null||comparator==null){ + return; + } + mChildes.sort(comparator); + } + public void sortAttributesWithChildes(Comparator comparator){ + if(comparator==null){ + return; + } + sortAttributes(comparator); + if(mChildes!=null){ + for(XMLElement element:mChildes){ + element.sortAttributesWithChildes(comparator); + } + } + } + public void sortAttributes(Comparator comparator){ + if(mAttributes==null || comparator==null){ + return; + } + mAttributes.sort(comparator); + } + public XMLElement getParent(){ + return mParent; + } + void setParent(XMLElement baseElement){ + mParent=baseElement; + } + private void addChildNoCheck(XMLElement baseElement){ + if(baseElement==null){ + return; + } + if(mChildes==null){ + mChildes=new ArrayList<>(); + } + baseElement.setParent(this); + baseElement.setIndent(getChildIndent()); + mChildes.add(baseElement); + } + public int getLevel(){ + int rs=0; + XMLElement parent=getParent(); + if(parent!=null){ + rs=rs+1; + rs+=parent.getLevel(); + } + return rs; + } + int getIndent(){ + return mIndent; + } + int getChildIndent(){ + if(mIndent<=0){ + return 0; + } + int rs=mIndent+1; + String tag= getTagName(); + if(tag!=null){ + int i=tag.length(); + if(i>10){ + i=10; + } + rs+=i; + } + return rs; + } + public void setIndent(int indent){ + mIndent=indent; + if(mChildes!=null){ + int chIndent=getChildIndent(); + for(XMLElement be:mChildes){ + be.setIndent(chIndent); + } + } + if(mComments!=null){ + for(XMLComment ce:mComments){ + ce.setIndent(indent); + } + } + } + private String getAttributesIndentText(){ + int i=getLevel()+1; + String tagName=getTagName(); + if(tagName!=null){ + i+=tagName.length(); + } + if(i>12){ + i=12; + } + tagName=""; + while (tagName.length()15){ + i=15; + } + i+=getIndentWidth(); + int j=0; + while (j40){ + i=40; + } + return i; + } + private String getIndentText(){ + float scale=getIndentScale(); + scale = scale * (float) getIndent(); + int i=(int)scale; + if(i<=0){ + return ""; + } + if(i>40){ + i=40; + } + StringBuilder builder=new StringBuilder(); + int max=i; + i=0; + while (i0){ + return false; + } + if(mComments!=null && mComments.size()>0){ + return false; + } + return getTextContent()==null; + } + private boolean canAppendChildes(){ + if(mChildes==null){ + return false; + } + for(XMLElement be:mChildes){ + if (!be.isEmpty()){ + return true; + } + } + return false; + } + boolean appendComments(Writer writer) throws IOException { + if(mComments==null){ + return false; + } + boolean appendPrevious=false; + boolean addedOnce=false; + for(XMLComment ce:mComments){ + if(ce.isEmpty()){ + continue; + } + if(appendPrevious){ + writer.write(XMLUtil.NEW_LINE); + } + appendPrevious=ce.write(writer, false); + if(appendPrevious){ + addedOnce=true; + } + } + return addedOnce; + } + private boolean appendChildes(Writer writer, boolean newLineAttributes) throws IOException { + if(mChildes==null){ + return false; + } + boolean appendPrevious=true; + boolean addedOnce=false; + for(XMLElement be:mChildes){ + if(stopWriting(writer)){ + break; + } + if(be.isEmpty()){ + continue; + } + if(appendPrevious){ + writer.write(XMLUtil.NEW_LINE); + } + appendPrevious=be.write(writer, newLineAttributes); + if(!addedOnce && appendPrevious){ + addedOnce=true; + } + } + return addedOnce; + } + private boolean stopWriting(Writer writer){ + if(!(writer instanceof ElementWriter)){ + return false; + } + ElementWriter elementWriter=(ElementWriter)writer; + if(elementWriter.isFinished()){ + elementWriter.writeInterrupted(); + return true; + } + return false; + } + public boolean write(Writer writer, boolean newLineAttributes) throws IOException { + if(isEmpty()){ + return false; + } + if(stopWriting(writer)){ + return false; + } + boolean appendOnce=appendComments(writer); + if(appendOnce){ + writer.write(XMLUtil.NEW_LINE); + } + appendIndentText(writer); + writer.write(mStart); + String tagName=getTagName(); + if(tagName!=null){ + writer.write(tagName); + } + appendAttributes(writer, newLineAttributes); + boolean useEndTag=false; + if(canAppendChildes()){ + writer.write(mEnd); + appendChildes(writer, newLineAttributes); + useEndTag=true; + } + boolean hasTextCon=hasTextContent(); + if(hasTextCon){ + if(!useEndTag){ + writer.write(mEnd); + }else { + writer.write(XMLUtil.NEW_LINE); + } + appendTextContent(writer); + useEndTag=true; + } + if(useEndTag){ + if(!hasTextCon){ + writer.write(XMLUtil.NEW_LINE); + appendIndentText(writer); + } + writer.write(mStart); + writer.write(mStartPrefix); + writer.write(getTagName()); + writer.write(mEnd); + }else { + writer.write(mEndPrefix); + writer.write(mEnd); + } + return true; + } + public String toText(){ + return toText(1, false); + } + public String toText(boolean newLineAttributes){ + return toText(1, newLineAttributes); + } + public String toText(int indent, boolean newLineAttributes){ + StringWriter writer=new StringWriter(); + setIndent(indent); + try { + write(writer, newLineAttributes); + writer.flush(); + writer.close(); + } catch (IOException ignored) { + } + return writer.toString(); + } + @Override + public String toString(){ + StringWriter strWriter=new StringWriter(); + ElementWriter writer=new ElementWriter(strWriter, DEBUG_TO_STRING); + try { + write(writer, false); + } catch (IOException e) { + } + strWriter.flush(); + return strWriter.toString(); + } + + public static List getAllElementsRecursive(XMLElement parent){ + List results=new ArrayList<>(); + if(parent==null){ + return results; + } + XMLElement[] allChildes=parent.getAllChildes(); + if(allChildes==null){ + return results; + } + int max=allChildes.length; + for(int i=0;i"); + str=str.replaceAll("&", "&"); + str=str.replaceAll("<", "<"); + str=str.replaceAll(">", ">"); + return str; + } + public static String escapeQuote(String str){ + if(str==null){ + return null; + } + str=str.replaceAll("\"", """); + return str; + } + public static String unEscapeXmlChars(String str){ + if(str==null){ + return null; + } + str=str.replaceAll("&", "&"); + str=str.replaceAll("<", "<"); + str=str.replaceAll(">", ">"); + str=str.replaceAll(""", "\""); + if(str.startsWith("\"")&&str.endsWith("\"")){ + // str=str.substring(1, str.length()-1); + } + return str; + } + public static String intToHex(int val){ + return String.format("0x%08x", val); + } + public static int hexToInt(String hexStr, int def){ + if(hexStr==null){ + return def; + } + Matcher matcher=PATTERN_HEX.matcher(hexStr); + if(!matcher.find()){ + return def; + } + hexStr=matcher.group("A"); + return Integer.parseInt(hexStr, 16); + } + + public static String trimQuote(String txt){ + if(txt==null){ + return null; + } + String tmp=txt.trim(); + if(tmp.length()==0){ + return txt; + } + char c1=tmp.charAt(0); + if(c1!='"'){ + return txt; + } + int end=tmp.length()-1; + c1=tmp.charAt(end); + if(c1!='"'){ + return txt; + } + if(end<=1){ + return ""; + } + return tmp.substring(1,end); + } + public static boolean isStringEqual(String s1, String s2) { + if(s1==null&&s2==null){ + return true; + } + if(s1==null||s2==null){ + return false; + } + return s1.equals(s2); + } + + static String htmlToXml(String htmlString){ + if(htmlString==null){ + return null; + } + int i=0; + htmlString=htmlString.trim(); + String result=htmlString; + Matcher matcher=PATTERN_HTML_HEADER_CHILDES.matcher(htmlString); + while (matcher.find()){ + String openedTag=matcher.group("Element"); + if(openedTag.contains("https://github.githubassets.com")){ + openedTag.trim(); + } + int len=openedTag.length(); + String tagName=matcher.group("Tag"); + if(isOpenHtmlTag(tagName) && !openedTag.endsWith("/>")&& !openedTag.endsWith("/ >")){ + String rep=openedTag.substring(0, len-1); + rep=rep+"/>"; + result=result.replace(openedTag, rep); + result=result.replace(" crossorigin/>", "/>"); + result=result.replace(" data-pjax-transient/>", "/>"); + } + i=htmlString.indexOf(openedTag); + i=i+len; + htmlString=htmlString.substring(i); + htmlString=htmlString.trim(); + matcher=PATTERN_HTML_HEADER_CHILDES.matcher(htmlString); + } + result="\n"+result; + return result; + } + private static boolean isOpenHtmlTag(String tagName){ + if("link".equals(tagName)){ + return true; + } + if("meta".equals(tagName)){ + return true; + } + if("img".equals(tagName)){ + return true; + } + if("style".equals(tagName)){ + return true; + } + return false; + } + + private static Pattern PATTERN_HEX=Pattern.compile("^\\s*(0x)?(?[a-f0-9]+)\\s*$"); + + // + private static Pattern PATTERN_HTML_HEADER_CHILDES=Pattern.compile("(?<\\s*(?[a-zA-Z]+)\\s*[^<>]+>)"); + +} diff --git a/src/main/java/com/reandroid/xml/XmlHeaderElement.java b/src/main/java/com/reandroid/xml/XmlHeaderElement.java new file mode 100755 index 0000000..8a96b1b --- /dev/null +++ b/src/main/java/com/reandroid/xml/XmlHeaderElement.java @@ -0,0 +1,110 @@ + /* + * Copyright (C) 2022 github.com/REAndroid + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.reandroid.xml; + +public class XmlHeaderElement extends XMLElement { + private static final String ATTR_VERSION="version"; + private static final String ATTR_ENCODING="encoding"; + private static final String ATTR_STANDALONE="standalone"; + public XmlHeaderElement(XmlHeaderElement element){ + this(); + copyAll(element); + } + public XmlHeaderElement(){ + super(); + initializeStartEnd(); + setDefaultAttr(); + } + @Override + XMLElement onCloneElement(){ + return new XmlHeaderElement(this); + } + @Override + void cloneAllAttributes(XMLElement element){ + } + private void copyAll(XmlHeaderElement element){ + if(element==null){ + return; + } + int max=element.getAttributeCount(); + for(int i=0;i"); + setStartPrefix(""); + setEndPrefix(""); + } + private void setDefaultAttr(){ + setVersion("1.0"); + setEncoding("utf-8"); + setStandalone(null); + } + public Object getProperty(String name){ + XMLAttribute attr=getAttribute(name); + if(attr==null){ + return null; + } + String val=attr.getValue(); + if(ATTR_STANDALONE.equalsIgnoreCase(name)){ + boolean res=false; + if("true".equals(val)){ + res=true; + } + return res; + } + return val; + } + public void setProperty(String name, Object o){ + if(ATTR_STANDALONE.equalsIgnoreCase(name)){ + if(o instanceof Boolean){ + setStandalone((Boolean)o); + return; + } + } + String val=null; + if(o!=null){ + val=o.toString(); + } + setAttribute(name, val); + } + public void setVersion(String version){ + setAttribute(ATTR_VERSION, version); + } + public void setEncoding(String encoding){ + setAttribute(ATTR_ENCODING, encoding); + } + public void setStandalone(Boolean flag){ + if(flag==null){ + removeAttribute(ATTR_STANDALONE); + return; + } + String str=flag?"yes":"no"; + setAttribute(ATTR_STANDALONE, str); + } + @Override + int getChildIndent(){ + return 0; + } + @Override + int getIndent(){ + return 0; + } +} diff --git a/src/main/java/com/reandroid/xml/parser/AttrIDProvider.java b/src/main/java/com/reandroid/xml/parser/AttrIDProvider.java new file mode 100755 index 0000000..5fb86fa --- /dev/null +++ b/src/main/java/com/reandroid/xml/parser/AttrIDProvider.java @@ -0,0 +1,21 @@ + /* + * Copyright (C) 2022 github.com/REAndroid + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.reandroid.xml.parser; + +public interface AttrIDProvider { + int getAttributeNameResourceId(int position); + int getAttributeValueResourceId(int position); +} diff --git a/src/main/java/com/reandroid/xml/parser/MXParser.java b/src/main/java/com/reandroid/xml/parser/MXParser.java new file mode 100644 index 0000000..451125d --- /dev/null +++ b/src/main/java/com/reandroid/xml/parser/MXParser.java @@ -0,0 +1,2726 @@ +/* + * This class is taken from org.xmlpull.* + * + * Check license: http://xmlpull.org + * + */ + +/*This package is renamed from org.xmlpull.* to avoid conflicts*/ +package com.reandroid.xml.parser; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.UnsupportedEncodingException; +import com.reandroid.xml.parser.XmlPullParser; +import com.reandroid.xml.parser.XmlPullParserException; + +public class MXParser implements XmlPullParser +{ + protected final static String XML_URI = "http://www.w3.org/XML/1998/namespace"; + protected final static String XMLNS_URI = "http://www.w3.org/2000/xmlns/"; + protected final static String FEATURE_XML_ROUNDTRIP= + //"http://xmlpull.org/v1/doc/features.html#xml-roundtrip"; + "http://xmlpull.org/v1/doc/features.html#xml-roundtrip"; + protected final static String FEATURE_NAMES_INTERNED = + "http://xmlpull.org/v1/doc/features.html#names-interned"; + protected final static String PROPERTY_XMLDECL_VERSION = + "http://xmlpull.org/v1/doc/properties.html#xmldecl-version"; + protected final static String PROPERTY_XMLDECL_STANDALONE = + "http://xmlpull.org/v1/doc/properties.html#xmldecl-standalone"; + protected final static String PROPERTY_XMLDECL_CONTENT = + "http://xmlpull.org/v1/doc/properties.html#xmldecl-content"; + protected final static String PROPERTY_LOCATION = + "http://xmlpull.org/v1/doc/properties.html#location"; + + protected boolean allStringsInterned; + + protected void resetStringCache() { + } + + protected String newString(char[] cbuf, int off, int len) { + return new String(cbuf, off, len); + } + + protected String newStringIntern(char[] cbuf, int off, int len) { + return (new String(cbuf, off, len)).intern(); + } + + private static final boolean TRACE_SIZING = false; + + protected boolean processNamespaces; + protected boolean roundtripSupported; + + protected String location; + protected int lineNumber; + protected int columnNumber; + protected boolean seenRoot; + protected boolean reachedEnd; + protected int eventType; + protected boolean emptyElementTag; + + protected int depth; + protected char[][] elRawName; + protected int[] elRawNameEnd; + protected int[] elRawNameLine; + + protected String[] elName; + protected String[] elPrefix; + protected String[] elUri; + //protected String elValue[]; + protected int[] elNamespaceCount; + + protected void ensureElementsCapacity() { + final int elStackSize = elName != null ? elName.length : 0; + if( (depth + 1) >= elStackSize) { + // we add at least one extra slot ... + final int newSize = (depth >= 7 ? 2 * depth : 8) + 2; // = lucky 7 + 1 //25 + final boolean needsCopying = elStackSize > 0; + String[] arr = null; + arr = new String[newSize]; + if(needsCopying) System.arraycopy(elName, 0, arr, 0, elStackSize); + elName = arr; + arr = new String[newSize]; + if(needsCopying) System.arraycopy(elPrefix, 0, arr, 0, elStackSize); + elPrefix = arr; + arr = new String[newSize]; + if(needsCopying) System.arraycopy(elUri, 0, arr, 0, elStackSize); + elUri = arr; + + int[] iarr = new int[newSize]; + if(needsCopying) { + System.arraycopy(elNamespaceCount, 0, iarr, 0, elStackSize); + } else { + iarr[0] = 0; + } + elNamespaceCount = iarr; + iarr = new int[newSize]; + if(needsCopying) { + System.arraycopy(elRawNameEnd, 0, iarr, 0, elStackSize); + } + elRawNameEnd = iarr; + + iarr = new int[newSize]; + if(needsCopying) { + System.arraycopy(elRawNameLine, 0, iarr, 0, elStackSize); + } + elRawNameLine = iarr; + + final char[][] carr = new char[newSize][]; + if(needsCopying) { + System.arraycopy(elRawName, 0, carr, 0, elStackSize); + } + elRawName = carr; + } + } + protected int attributeCount; + protected String[] attributeName; + protected int[] attributeNameHash; + protected String[] attributePrefix; + protected String[] attributeUri; + protected String[] attributeValue; + + /** + * Make sure that in attributes temporary array is enough space. + */ + protected void ensureAttributesCapacity(int size) { + final int attrPosSize = attributeName != null ? attributeName.length : 0; + if(size >= attrPosSize) { + final int newSize = size > 7 ? 2 * size : 8; // = lucky 7 + 1 //25 + + final boolean needsCopying = attrPosSize > 0; + String[] arr = null; + + arr = new String[newSize]; + if(needsCopying) System.arraycopy(attributeName, 0, arr, 0, attrPosSize); + attributeName = arr; + + arr = new String[newSize]; + if(needsCopying) System.arraycopy(attributePrefix, 0, arr, 0, attrPosSize); + attributePrefix = arr; + + arr = new String[newSize]; + if(needsCopying) System.arraycopy(attributeUri, 0, arr, 0, attrPosSize); + attributeUri = arr; + + arr = new String[newSize]; + if(needsCopying) System.arraycopy(attributeValue, 0, arr, 0, attrPosSize); + attributeValue = arr; + + if( ! allStringsInterned ) { + final int[] iarr = new int[newSize]; + if(needsCopying) System.arraycopy(attributeNameHash, 0, iarr, 0, attrPosSize); + attributeNameHash = iarr; + } + + arr = null; + } + } + protected int namespaceEnd; + protected String namespacePrefix[]; + protected int namespacePrefixHash[]; + protected String namespaceUri[]; + + protected void ensureNamespacesCapacity(int size) { + final int namespaceSize = namespacePrefix != null ? namespacePrefix.length : 0; + if(size >= namespaceSize) { + final int newSize = size > 7 ? 2 * size : 8; // = lucky 7 + 1 //25 + + final String[] newNamespacePrefix = new String[newSize]; + final String[] newNamespaceUri = new String[newSize]; + if(namespacePrefix != null) { + System.arraycopy( + namespacePrefix, 0, newNamespacePrefix, 0, namespaceEnd); + System.arraycopy( + namespaceUri, 0, newNamespaceUri, 0, namespaceEnd); + } + namespacePrefix = newNamespacePrefix; + namespaceUri = newNamespaceUri; + + + if( ! allStringsInterned ) { + final int[] newNamespacePrefixHash = new int[newSize]; + if(namespacePrefixHash != null) { + System.arraycopy( + namespacePrefixHash, 0, newNamespacePrefixHash, 0, namespaceEnd); + } + namespacePrefixHash = newNamespacePrefixHash; + } + } + } + protected static int fastHash(char ch[], int off, int len ) { + if(len == 0) { + return 0; + } + int hash = ch[off]; + hash = (hash << 7) + ch[ off + len - 1 ]; + if(len > 16) { + hash = (hash << 7) + ch[ off + (len / 4)]; + } + if(len > 8) { + hash = (hash << 7) + ch[ off + (len / 2)]; + } + return hash; + } + protected int entityEnd; + + protected String entityName[]; + protected char[] entityNameBuf[]; + protected String entityReplacement[]; + protected char[] entityReplacementBuf[]; + + protected int entityNameHash[]; + + protected void ensureEntityCapacity() { + final int entitySize = entityReplacementBuf != null ? entityReplacementBuf.length : 0; + if(entityEnd >= entitySize) { + final int newSize = entityEnd > 7 ? 2 * entityEnd : 8; // = lucky 7 + 1 //25 + final String[] newEntityName = new String[newSize]; + final char[][] newEntityNameBuf = new char[newSize][]; + final String[] newEntityReplacement = new String[newSize]; + final char[][] newEntityReplacementBuf = new char[newSize][]; + if(entityName != null) { + System.arraycopy(entityName, 0, newEntityName, 0, entityEnd); + System.arraycopy(entityNameBuf, 0, newEntityNameBuf, 0, entityEnd); + System.arraycopy(entityReplacement, 0, newEntityReplacement, 0, entityEnd); + System.arraycopy(entityReplacementBuf, 0, newEntityReplacementBuf, 0, entityEnd); + } + entityName = newEntityName; + entityNameBuf = newEntityNameBuf; + entityReplacement = newEntityReplacement; + entityReplacementBuf = newEntityReplacementBuf; + + if( ! allStringsInterned ) { + final int[] newEntityNameHash = new int[newSize]; + if(entityNameHash != null) { + System.arraycopy(entityNameHash, 0, newEntityNameHash, 0, entityEnd); + } + entityNameHash = newEntityNameHash; + } + } + } + protected static final int READ_CHUNK_SIZE = 8*1024; + protected Reader reader; + protected String inputEncoding; + protected InputStream inputStream; + + + protected int bufLoadFactor = 95; + + protected char buf[] = new char[READ_CHUNK_SIZE]; + protected int bufSoftLimit = ( bufLoadFactor * buf.length ) /100; + protected boolean preventBufferCompaction; + + protected int bufAbsoluteStart; // this is buf + protected int bufStart; + protected int bufEnd; + protected int pos; + protected int posStart; + protected int posEnd; + + protected char[] pc = new char[ + Runtime.getRuntime().freeMemory() > 1000000L ? READ_CHUNK_SIZE : 64 ]; + protected int pcStart; + protected int pcEnd; + + protected boolean usePC; + + + protected boolean seenStartTag; + protected boolean seenEndTag; + protected boolean pastEndTag; + protected boolean seenAmpersand; + protected boolean seenMarkup; + protected boolean seenDocdecl; + + protected boolean tokenize; + protected String text; + protected String entityRefName; + + protected String xmlDeclVersion; + protected Boolean xmlDeclStandalone; + protected String xmlDeclContent; + + protected void reset() { + location = null; + lineNumber = 1; + columnNumber = 0; + seenRoot = false; + reachedEnd = false; + eventType = START_DOCUMENT; + emptyElementTag = false; + + depth = 0; + + attributeCount = 0; + + namespaceEnd = 0; + + entityEnd = 0; + + reader = null; + inputEncoding = null; + + preventBufferCompaction = false; + bufAbsoluteStart = 0; + bufEnd = bufStart = 0; + pos = posStart = posEnd = 0; + + pcEnd = pcStart = 0; + + usePC = false; + + seenStartTag = false; + seenEndTag = false; + pastEndTag = false; + seenAmpersand = false; + seenMarkup = false; + seenDocdecl = false; + + xmlDeclVersion = null; + xmlDeclStandalone = null; + xmlDeclContent = null; + + resetStringCache(); + } + + public MXParser() { + } + public void setFeature(String name, boolean state) throws XmlPullParserException + { + if(name == null) throw new IllegalArgumentException("feature name should not be null"); + if(FEATURE_PROCESS_NAMESPACES.equals(name)) { + if(eventType != START_DOCUMENT) throw new XmlPullParserException( + "namespace processing feature can only be changed before parsing", this, null); + processNamespaces = state; + } else if(FEATURE_NAMES_INTERNED.equals(name)) { + if(state != false) { + throw new XmlPullParserException( + "interning names in this implementation is not supported"); + } + } else if(FEATURE_PROCESS_DOCDECL.equals(name)) { + if(state != false) { + throw new XmlPullParserException( + "processing DOCDECL is not supported"); + } + } else if(FEATURE_XML_ROUNDTRIP.equals(name)) { + roundtripSupported = state; + } else { + throw new XmlPullParserException("unsupported feature "+name); + } + } + + public boolean getFeature(String name) + { + if(name == null) throw new IllegalArgumentException("feature name should not be null"); + if(FEATURE_PROCESS_NAMESPACES.equals(name)) { + return processNamespaces; + } else if(FEATURE_NAMES_INTERNED.equals(name)) { + return false; + } else if(FEATURE_PROCESS_DOCDECL.equals(name)) { + return false; + } else if(FEATURE_XML_ROUNDTRIP.equals(name)) { + return roundtripSupported; + } + return false; + } + + public void setProperty(String name, + Object value) + throws XmlPullParserException + { + if(PROPERTY_LOCATION.equals(name)) { + location = (String) value; + } else { + throw new XmlPullParserException("unsupported property: '"+name+"'"); + } + } + + + public Object getProperty(String name) + { + if(name == null) throw new IllegalArgumentException("property name should not be null"); + if(PROPERTY_XMLDECL_VERSION.equals(name)) { + return xmlDeclVersion; + } else if(PROPERTY_XMLDECL_STANDALONE.equals(name)) { + return xmlDeclStandalone; + } else if(PROPERTY_XMLDECL_CONTENT.equals(name)) { + return xmlDeclContent; + } else if(PROPERTY_LOCATION.equals(name)) { + return location; + } + return null; + } + + + public void setInput(Reader in) throws XmlPullParserException + { + reset(); + reader = in; + } + + public void setInput(java.io.InputStream inputStream, String inputEncoding) + throws XmlPullParserException + { + if(inputStream == null) { + throw new IllegalArgumentException("input stream can not be null"); + } + this.inputStream = inputStream; + Reader reader; + try { + if(inputEncoding != null) { + reader = new InputStreamReader(inputStream, inputEncoding); + } else { + reader = new InputStreamReader(inputStream, "UTF-8"); + } + } catch (UnsupportedEncodingException une) { + throw new XmlPullParserException( + "could not create reader for encoding "+inputEncoding+" : "+une, this, une); + } + setInput(reader); + this.inputEncoding = inputEncoding; + } + public InputStream getInputStream(){ + return inputStream; + } + + public String getInputEncoding() { + return inputEncoding; + } + + public void defineEntityReplacementText(String entityName, + String replacementText) + throws XmlPullParserException + { + ensureEntityCapacity(); + + // this is to make sure that if interning works we will take advantage of it ... + this.entityName[entityEnd] = newString(entityName.toCharArray(), 0, entityName.length()); + entityNameBuf[entityEnd] = entityName.toCharArray(); + + entityReplacement[entityEnd] = replacementText; + entityReplacementBuf[entityEnd] = replacementText.toCharArray(); + if(!allStringsInterned) { + entityNameHash[ entityEnd ] = + fastHash(entityNameBuf[entityEnd], 0, entityNameBuf[entityEnd].length); + } + ++entityEnd; + } + + public int getNamespaceCount(int depth) throws XmlPullParserException + { + if(!processNamespaces || depth == 0) { + return 0; + } + if(depth < 0 || depth > this.depth) { + throw new IllegalArgumentException( + "allowed namespace depth 0.."+this.depth+" not "+depth); + } + return elNamespaceCount[ depth ]; + } + + public String getNamespacePrefix(int pos) + throws XmlPullParserException + { + if(pos < namespaceEnd) { + return namespacePrefix[ pos ]; + } else { + throw new XmlPullParserException( + "position "+pos+" exceeded number of available namespaces "+namespaceEnd); + } + } + + public String getNamespaceUri(int pos) throws XmlPullParserException + { + if(pos < namespaceEnd) { + return namespaceUri[ pos ]; + } else { + throw new XmlPullParserException( + "position "+pos+" exceeded number of available namespaces "+namespaceEnd); + } + } + + public String getNamespace( String prefix ) + { + if(prefix != null) { + for( int i = namespaceEnd -1; i >= 0; i--) { + if( prefix.equals( namespacePrefix[ i ] ) ) { + return namespaceUri[ i ]; + } + } + if("xml".equals( prefix )) { + return XML_URI; + } else if("xmlns".equals( prefix )) { + return XMLNS_URI; + } + } else { + for( int i = namespaceEnd -1; i >= 0; i--) { + if( namespacePrefix[ i ] == null) { //"") { //null ) { //TODO check FIXME Alek + return namespaceUri[ i ]; + } + } + + } + return null; + } + + + public int getDepth() + { + return depth; + } + + + private static int findFragment(int bufMinPos, char[] b, int start, int end) { + if(start < bufMinPos) { + start = bufMinPos; + if(start > end) start = end; + return start; + } + if(end - start > 65) { + start = end - 10; // try to find good location + } + int i = start + 1; + while(--i > bufMinPos) { + if((end - i) > 65) break; + final char c = b[i]; + if(c == '<' && (start - i) > 10) break; + } + return i; + } + public String getPositionDescription () + { + String fragment = null; + if(posStart <= pos) { + final int start = findFragment(0, buf, posStart, pos); + + if(start < pos) { + fragment = new String(buf, start, pos - start); + } + if(bufAbsoluteStart > 0 || start > 0) { + fragment = "..." + fragment; + } + } + return " "+TYPES[ eventType ] + + (fragment != null ? " seen "+printable(fragment)+"..." : "") + +" "+(location != null ? location : "") + +"@"+getLineNumber()+":"+getColumnNumber(); + } + + public int getLineNumber() + { + return lineNumber; + } + + public int getColumnNumber() + { + return columnNumber; + } + + + public boolean isWhitespace() throws XmlPullParserException + { + if(eventType == TEXT || eventType == CDSECT) { + if(usePC) { + for (int i = pcStart; i = attributeCount) throw new IndexOutOfBoundsException( + "attribute position must be 0.."+(attributeCount-1)+" and not "+index); + return attributeUri[ index ]; + } + + public String getAttributeName(int index) + { + if(eventType != START_TAG) throw new IndexOutOfBoundsException( + "only START_TAG can have attributes"); + if(index < 0 || index >= attributeCount) throw new IndexOutOfBoundsException( + "attribute position must be 0.."+(attributeCount-1)+" and not "+index); + return attributeName[ index ]; + } + + public String getAttributePrefix(int index) + { + if(eventType != START_TAG) throw new IndexOutOfBoundsException( + "only START_TAG can have attributes"); + if(processNamespaces == false) return null; + if(index < 0 || index >= attributeCount) throw new IndexOutOfBoundsException( + "attribute position must be 0.."+(attributeCount-1)+" and not "+index); + return attributePrefix[ index ]; + } + + public String getAttributeType(int index) { + if(eventType != START_TAG) throw new IndexOutOfBoundsException( + "only START_TAG can have attributes"); + if(index < 0 || index >= attributeCount) throw new IndexOutOfBoundsException( + "attribute position must be 0.."+(attributeCount-1)+" and not "+index); + return "CDATA"; + } + + public boolean isAttributeDefault(int index) { + if(eventType != START_TAG) throw new IndexOutOfBoundsException( + "only START_TAG can have attributes"); + if(index < 0 || index >= attributeCount) throw new IndexOutOfBoundsException( + "attribute position must be 0.."+(attributeCount-1)+" and not "+index); + return false; + } + + public String getAttributeValue(int index) + { + if(eventType != START_TAG) throw new IndexOutOfBoundsException( + "only START_TAG can have attributes"); + if(index < 0 || index >= attributeCount) throw new IndexOutOfBoundsException( + "attribute position must be 0.."+(attributeCount-1)+" and not "+index); + return attributeValue[ index ]; + } + + public String getAttributeValue(String namespace, + String name) + { + if(eventType != START_TAG) throw new IndexOutOfBoundsException( + "only START_TAG can have attributes"+getPositionDescription()); + if(name == null) { + throw new IllegalArgumentException("attribute name can not be null"); + } + // TODO make check if namespace is interned!!! etc. for names!!! + if(processNamespaces) { + if(namespace == null) { + namespace = ""; + } + + for(int i = 0; i < attributeCount; ++i) { + if((namespace == attributeUri[ i ] || + namespace.equals(attributeUri[ i ]) ) + //(namespace != null && namespace.equals(attributeUri[ i ])) + // taking advantage of String.intern() + && name.equals(attributeName[ i ]) ) + { + return attributeValue[i]; + } + } + } else { + if(namespace != null && namespace.length() == 0) { + namespace = null; + } + if(namespace != null) throw new IllegalArgumentException( + "when namespaces processing is disabled attribute namespace must be null"); + for(int i = 0; i < attributeCount; ++i) { + if(name.equals(attributeName[i])) + { + return attributeValue[i]; + } + } + } + return null; + } + + + public int getEventType() + throws XmlPullParserException + { + return eventType; + } + + public void require(int type, String namespace, String name) + throws XmlPullParserException, IOException + { + if(processNamespaces == false && namespace != null) { + throw new XmlPullParserException( + "processing namespaces must be enabled on parser (or factory)"+ + " to have possible namespaces declared on elements" + +(" (position:"+ getPositionDescription())+")"); + } + if (type != getEventType() + || (namespace != null && !namespace.equals (getNamespace())) + || (name != null && !name.equals (getName ())) ) + { + throw new XmlPullParserException ( + "expected event "+TYPES[ type ] + +(name != null ? " with name '"+name+"'" : "") + +(namespace != null && name != null ? " and" : "") + +(namespace != null ? " with namespace '"+namespace+"'" : "") + +" but got" + +(type != getEventType() ? " "+TYPES[ getEventType() ] : "") + +(name != null && getName() != null && !name.equals (getName ()) + ? " name '"+getName()+"'" : "") + +(namespace != null && name != null + && getName() != null && !name.equals (getName ()) + && getNamespace() != null && !namespace.equals (getNamespace()) + ? " and" : "") + +(namespace != null && getNamespace() != null && !namespace.equals (getNamespace()) + ? " namespace '"+getNamespace()+"'" : "") + +(" (position:"+ getPositionDescription())+")"); + } + } + + public void skipSubTree() + throws XmlPullParserException, IOException + { + require(START_TAG, null, null); + int level = 1; + while(level > 0) { + int eventType = next(); + if(eventType == END_TAG) { + --level; + } else if(eventType == START_TAG) { + ++level; + } + } + } + + 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) { + final String result = getText(); + eventType = next(); + if(eventType != END_TAG) { + throw new XmlPullParserException( + "TEXT must be immediately followed by END_TAG and not " + +TYPES[ getEventType() ], 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 int nextTag() throws XmlPullParserException, IOException + { + next(); + if(eventType == TEXT && isWhitespace()) { // skip whitespace + next(); + } + if (eventType != START_TAG && eventType != END_TAG) { + throw new XmlPullParserException("expected START_TAG or END_TAG not " + +TYPES[ getEventType() ], this, null); + } + return eventType; + } + + public int next() + throws XmlPullParserException, IOException + { + tokenize = false; + return nextImpl(); + } + + public int nextToken() + throws XmlPullParserException, IOException + { + tokenize = true; + return nextImpl(); + } + + + protected int nextImpl() + throws XmlPullParserException, IOException + { + text = null; + pcEnd = pcStart = 0; + usePC = false; + bufStart = posEnd; + if(pastEndTag) { + pastEndTag = false; + --depth; + namespaceEnd = elNamespaceCount[ depth ]; // less namespaces available + } + if(emptyElementTag) { + emptyElementTag = false; + pastEndTag = true; + return eventType = END_TAG; + } + if(depth > 0) { + + if(seenStartTag) { + seenStartTag = false; + return eventType = parseStartTag(); + } + if(seenEndTag) { + seenEndTag = false; + return eventType = parseEndTag(); + } + + char ch; + if(seenMarkup) { // we have read ahead ... + seenMarkup = false; + ch = '<'; + } else if(seenAmpersand) { + seenAmpersand = false; + ch = '&'; + } else { + ch = more(); + } + posStart = pos - 1; // VERY IMPORTANT: this is correct start of event!!! + + // when true there is some potential event TEXT to return - keep gathering + boolean hadCharData = false; + + // when true TEXT data is not continual (like ) and requires PC merging + boolean needsMerging = false; + + MAIN_LOOP: + while(true) { + // work on MARKUP + if(ch == '<') { + if(hadCharData) { + //posEnd = pos - 1; + if(tokenize) { + seenMarkup = true; + return eventType = TEXT; + } + } + ch = more(); + if(ch == '/') { + if(!tokenize && hadCharData) { + seenEndTag = true; + //posEnd = pos - 2; + return eventType = TEXT; + } + return eventType = parseEndTag(); + } else if(ch == '!') { + ch = more(); + if(ch == '-') { + // note: if(tokenize == false) posStart/End is NOT changed!!!! + parseComment(); + if(tokenize) return eventType = COMMENT; + if( !usePC && hadCharData ) { + needsMerging = true; + } else { + posStart = pos; //completely ignore comment + } + } else if(ch == '[') { + parseCDSect(hadCharData); + if(tokenize) return eventType = CDSECT; + final int cdStart = posStart; + final int cdEnd = posEnd; + final int cdLen = cdEnd - cdStart; + + + if(cdLen > 0) { // was there anything inside CDATA section? + hadCharData = true; + if(!usePC) { + needsMerging = true; + } + } + } else { + throw new XmlPullParserException( + "unexpected character in markup "+printable(ch), this, null); + } + } else if(ch == '?') { + parsePI(); + if(tokenize) return eventType = PROCESSING_INSTRUCTION; + if( !usePC && hadCharData ) { + needsMerging = true; + } else { + posStart = pos; //completely ignore PI + } + + } else if( isNameStartChar(ch) ) { + if(!tokenize && hadCharData) { + seenStartTag = true; + //posEnd = pos - 2; + return eventType = TEXT; + } + return eventType = parseStartTag(); + } else { + throw new XmlPullParserException( + "unexpected character in markup "+printable(ch), this, null); + } + + } else if(ch == '&') { + // work on ENTITTY + //posEnd = pos - 1; + if(tokenize && hadCharData) { + seenAmpersand = true; + return eventType = TEXT; + } + final int oldStart = posStart + bufAbsoluteStart; + final int oldEnd = posEnd + bufAbsoluteStart; + final char[] resolvedEntity = parseEntityRef(); + if(tokenize) return eventType = ENTITY_REF; + // check if replacement text can be resolved !!! + if(resolvedEntity == null) { + if(entityRefName == null) { + entityRefName = newString(buf, posStart, posEnd - posStart); + } + throw new XmlPullParserException( + "could not resolve entity named '"+printable(entityRefName)+"'", + this, null); + } + //int entStart = posStart; + //int entEnd = posEnd; + posStart = oldStart - bufAbsoluteStart; + posEnd = oldEnd - bufAbsoluteStart; + if(!usePC) { + if(hadCharData) { + joinPC(); // posEnd is already set correctly!!! + needsMerging = false; + } else { + usePC = true; + pcStart = pcEnd = 0; + } + } + for (int i = 0; i < resolvedEntity.length; i++) + { + if(pcEnd >= pc.length) ensurePC(pcEnd); + pc[pcEnd++] = resolvedEntity[ i ]; + + } + hadCharData = true; + } else { + + if(needsMerging) { + joinPC(); + needsMerging = false; + } + + hadCharData = true; + + boolean normalizedCR = false; + final boolean normalizeInput = tokenize == false || roundtripSupported == false; + + boolean seenBracket = false; + boolean seenBracketBracket = false; + do { + if(ch == ']') { + if(seenBracket) { + seenBracketBracket = true; + } else { + seenBracket = true; + } + } else if(seenBracketBracket && ch == '>') { + throw new XmlPullParserException( + "characters ]]> are not allowed in content", this, null); + } else { + if(seenBracket) { + seenBracketBracket = seenBracket = false; + } + // assert seenTwoBrackets == seenBracket == false; + } + if(normalizeInput) { + // deal with normalization issues ... + if(ch == '\r') { + normalizedCR = true; + posEnd = pos -1; + // posEnd is already is set + if(!usePC) { + if(posEnd > posStart) { + joinPC(); + } else { + usePC = true; + pcStart = pcEnd = 0; + } + } + //assert usePC == true; + if(pcEnd >= pc.length) ensurePC(pcEnd); + pc[pcEnd++] = '\n'; + } else if(ch == '\n') { + // if(!usePC) { joinPC(); } else { if(pcEnd >= pc.length) ensurePC(); } + if(!normalizedCR && usePC) { + if(pcEnd >= pc.length) ensurePC(pcEnd); + pc[pcEnd++] = '\n'; + } + normalizedCR = false; + } else { + if(usePC) { + if(pcEnd >= pc.length) ensurePC(pcEnd); + pc[pcEnd++] = ch; + } + normalizedCR = false; + } + } + + ch = more(); + } while(ch != '<' && ch != '&'); + posEnd = pos - 1; + continue MAIN_LOOP; + } + ch = more(); + } // endless while(true) + } else { + if(seenRoot) { + return parseEpilog(); + } else { + return parseProlog(); + } + } + } + + + protected int parseProlog() + throws XmlPullParserException, IOException + { + char ch; + if(seenMarkup) { + ch = buf[ pos - 1 ]; + } else { + ch = more(); + } + + if(eventType == START_DOCUMENT) { + if(ch == '\uFFFE') { + throw new XmlPullParserException( + "first character in input was UNICODE noncharacter (0xFFFE)"+ + "- input requires int swapping", this, null); + } + if(ch == '\uFEFF') { + ch = more(); + } + } + seenMarkup = false; + boolean gotS = false; + posStart = pos - 1; + final boolean normalizeIgnorableWS = tokenize == true && roundtripSupported == false; + boolean normalizedCR = false; + while(true) { + if(ch == '<') { + if(gotS && tokenize) { + posEnd = pos - 1; + seenMarkup = true; + return eventType = IGNORABLE_WHITESPACE; + } + ch = more(); + if(ch == '?') { + if(parsePI()) { // make sure to skip XMLDecl + if(tokenize) { + return eventType = PROCESSING_INSTRUCTION; + } + } else { + // skip over - continue tokenizing + posStart = pos; + gotS = false; + } + + } else if(ch == '!') { + ch = more(); + if(ch == 'D') { + if(seenDocdecl) { + throw new XmlPullParserException( + "only one docdecl allowed in XML document", this, null); + } + seenDocdecl = true; + parseDocdecl(); + if(tokenize) return eventType = DOCDECL; + } else if(ch == '-') { + parseComment(); + if(tokenize) return eventType = COMMENT; + } else { + throw new XmlPullParserException( + "unexpected markup posStart) { + joinPC(); + } else { + usePC = true; + pcStart = pcEnd = 0; + } + } + //assert usePC == true; + if(pcEnd >= pc.length) ensurePC(pcEnd); + pc[pcEnd++] = '\n'; + } else if(ch == '\n') { + if(!normalizedCR && usePC) { + if(pcEnd >= pc.length) ensurePC(pcEnd); + pc[pcEnd++] = '\n'; + } + normalizedCR = false; + } else { + if(usePC) { + if(pcEnd >= pc.length) ensurePC(pcEnd); + pc[pcEnd++] = ch; + } + normalizedCR = false; + } + } + } else { + throw new XmlPullParserException( + "only whitespace content allowed before start tag and not "+printable(ch), + this, null); + } + ch = more(); + } + } + + protected int parseEpilog() + throws XmlPullParserException, IOException + { + if(eventType == END_DOCUMENT) { + throw new XmlPullParserException("already reached end of XML input", this, null); + } + if(reachedEnd) { + return eventType = END_DOCUMENT; + } + boolean gotS = false; + final boolean normalizeIgnorableWS = tokenize && !roundtripSupported; + boolean normalizedCR = false; + try { + char ch; + if(seenMarkup) { + ch = buf[ pos - 1 ]; + } else { + ch = more(); + } + seenMarkup = false; + posStart = pos - 1; + if(!reachedEnd) { + while(true) { + if(ch == '<') { + if(gotS && tokenize) { + posEnd = pos - 1; + seenMarkup = true; + return eventType = IGNORABLE_WHITESPACE; + } + ch = more(); + if(reachedEnd) { + break; + } + if(ch == '?') { + parsePI(); + if(tokenize) return eventType = PROCESSING_INSTRUCTION; + + } else if(ch == '!') { + ch = more(); + if(reachedEnd) { + break; + } + if(ch == 'D') { + parseDocdecl(); + if(tokenize) { + return eventType = DOCDECL; + } + } else if(ch == '-') { + parseComment(); + if(tokenize) return eventType = COMMENT; + } else { + throw new XmlPullParserException( + "unexpected markup posStart) { + joinPC(); + } else { + usePC = true; + pcStart = pcEnd = 0; + } + } + //assert usePC == true; + if(pcEnd >= pc.length) ensurePC(pcEnd); + pc[pcEnd++] = '\n'; + } else if(ch == '\n') { + if(!normalizedCR && usePC) { + if(pcEnd >= pc.length) ensurePC(pcEnd); + pc[pcEnd++] = '\n'; + } + normalizedCR = false; + } else { + if(usePC) { + if(pcEnd >= pc.length) ensurePC(pcEnd); + pc[pcEnd++] = ch; + } + normalizedCR = false; + } + } + } else { + throw new XmlPullParserException( + "in epilog non whitespace content is not allowed but got "+printable(ch), + this, null); + } + ch = more(); + if(reachedEnd) { + break; + } + + } + } + } catch(EOFException ex) { + reachedEnd = true; + } + if(reachedEnd) { + if(tokenize && gotS) { + posEnd = pos; // well - this is LAST available character pos + return eventType = IGNORABLE_WHITESPACE; + } + return eventType = END_DOCUMENT; + } else { + throw new XmlPullParserException("internal error in parseEpilog"); + } + } + + + public int parseEndTag() throws XmlPullParserException, IOException { + char ch = more(); + if(!isNameStartChar(ch)) { + throw new XmlPullParserException( + "expected name start and not "+printable(ch), this, null); + } + posStart = pos - 3; + final int nameStart = pos - 1 + bufAbsoluteStart; + do { + ch = more(); + } while(isNameChar(ch)); + + int off = nameStart - bufAbsoluteStart; + final int len = (pos - 1) - off; + final char[] cbuf = elRawName[depth]; + if(elRawNameEnd[depth] != len) { + // construct strings for exception + final String startname = new String(cbuf, 0, elRawNameEnd[depth]); + final String endname = new String(buf, off, len); + throw new XmlPullParserException( + "end tag name must match start tag name <"+startname+">" + +" from line "+elRawNameLine[depth], this, null); + } + for (int i = 0; i < len; i++) + { + if(buf[off++] != cbuf[i]) { + // construct strings for exception + final String startname = new String(cbuf, 0, len); + final String endname = new String(buf, off - i - 1, len); + throw new XmlPullParserException( + "end tag name must be the same as start tag <"+startname+">" + +" from line "+elRawNameLine[depth], this, null); + } + } + + while(isS(ch)) { + ch = more(); + } // skip additional white spaces + if(ch != '>') { + throw new XmlPullParserException( + "expected > to finish end tag not "+printable(ch) + +" from line "+elRawNameLine[depth], this, null); + } + posEnd = pos; + pastEndTag = true; + return eventType = END_TAG; + } + + public int parseStartTag() throws XmlPullParserException, IOException { + ++depth; + posStart = pos - 2; + emptyElementTag = false; + attributeCount = 0; + final int nameStart = pos - 1 + bufAbsoluteStart; + int colonPos = -1; + char ch = buf[ pos - 1]; + if(ch == ':' && processNamespaces) throw new XmlPullParserException( + "when namespaces processing enabled colon can not be at element name start", + this, null); + while(true) { + ch = more(); + if(!isNameChar(ch)) break; + if(ch == ':' && processNamespaces) { + if(colonPos != -1) throw new XmlPullParserException( + "only one colon is allowed in name of element when namespaces are enabled", + this, null); + colonPos = pos - 1 + bufAbsoluteStart; + } + } + ensureElementsCapacity(); + int elLen = (pos - 1) - (nameStart - bufAbsoluteStart); + if(elRawName[ depth ] == null || elRawName[ depth ].length < elLen) { + elRawName[ depth ] = new char[ 2 * elLen ]; + } + System.arraycopy(buf, nameStart - bufAbsoluteStart, elRawName[ depth ], 0, elLen); + elRawNameEnd[ depth ] = elLen; + elRawNameLine[ depth ] = lineNumber; + + String name = null; + + // work on prefixes and namespace URI + String prefix = null; + if(processNamespaces) { + if(colonPos != -1) { + prefix = elPrefix[ depth ] = newString(buf, nameStart - bufAbsoluteStart, + colonPos - nameStart); + name = elName[ depth ] = newString(buf, colonPos + 1 - bufAbsoluteStart, + //(pos -1) - (colonPos + 1)); + pos - 2 - (colonPos - bufAbsoluteStart)); + } else { + prefix = elPrefix[ depth ] = null; + name = elName[ depth ] = newString(buf, nameStart - bufAbsoluteStart, elLen); + } + } else { + name = elName[ depth ] = newString(buf, nameStart - bufAbsoluteStart, elLen); + } + + + while(true) { + while(isS(ch)) { + ch = more(); + } + + if(ch == '>') { + break; + } else if(ch == '/') { + if(emptyElementTag) throw new XmlPullParserException( + "repeated / in tag declaration", this, null); + emptyElementTag = true; + ch = more(); + if(ch != '>') throw new XmlPullParserException( + "expected > to end empty tag not "+printable(ch), this, null); + break; + } else if(isNameStartChar(ch)) { + ch = parseAttribute(); + ch = more(); + continue; + } else { + throw new XmlPullParserException( + "start tag unexpected character "+printable(ch), this, null); + } + } + + // now when namespaces were declared we can resolve them + if(processNamespaces) { + String uri = getNamespace(prefix); + if(uri == null) { + if(prefix == null) { // no prefix and no uri => use default namespace + uri = NO_NAMESPACE; + } else { + throw new XmlPullParserException( + "could not determine namespace bound to element prefix "+prefix, + this, null); + } + + } + elUri[ depth ] = uri; + + for (int i = 0; i < attributeCount; i++) + { + final String attrPrefix = attributePrefix[ i ]; + if(attrPrefix != null) { + final String attrUri = getNamespace(attrPrefix); + if(attrUri == null) { + throw new XmlPullParserException( + "could not determine namespace bound to attribute prefix "+attrPrefix, + this, null); + + } + attributeUri[ i ] = attrUri; + } else { + attributeUri[ i ] = NO_NAMESPACE; + } + } + + for (int i = 1; i < attributeCount; i++) + { + for (int j = 0; j < i; j++) + { + if( attributeUri[j] == attributeUri[i] + && (allStringsInterned && attributeName[j].equals(attributeName[i]) + || (!allStringsInterned + && attributeNameHash[ j ] == attributeNameHash[ i ] + && attributeName[j].equals(attributeName[i])) ) + + ) { + // prepare data for nice error message? + String attr1 = attributeName[j]; + if(attributeUri[j] != null) attr1 = attributeUri[j]+":"+attr1; + String attr2 = attributeName[i]; + if(attributeUri[i] != null) attr2 = attributeUri[i]+":"+attr2; + throw new XmlPullParserException( + "duplicated attributes "+attr1+" and "+attr2, this, null); + } + } + } + + + } else { + for (int i = 1; i < attributeCount; i++) + { + for (int j = 0; j < i; j++) + { + if((allStringsInterned && attributeName[j].equals(attributeName[i]) + || (!allStringsInterned + && attributeNameHash[ j ] == attributeNameHash[ i ] + && attributeName[j].equals(attributeName[i])) ) + + ) { + // prepare data for nice error message? + final String attr1 = attributeName[j]; + final String attr2 = attributeName[i]; + throw new XmlPullParserException( + "duplicated attributes "+attr1+" and "+attr2, this, null); + } + } + } + } + + elNamespaceCount[ depth ] = namespaceEnd; + posEnd = pos; + return eventType = START_TAG; + } + + protected char parseAttribute() throws XmlPullParserException, IOException + { + final int prevPosStart = posStart + bufAbsoluteStart; + final int nameStart = pos - 1 + bufAbsoluteStart; + int colonPos = -1; + char ch = buf[ pos - 1 ]; + if(ch == ':' && processNamespaces) throw new XmlPullParserException( + "when namespaces processing enabled colon can not be at attribute name start", + this, null); + + + boolean startsWithXmlns = processNamespaces && ch == 'x'; + int xmlnsPos = 0; + + ch = more(); + while(isNameChar(ch)) { + if(processNamespaces) { + if(startsWithXmlns && xmlnsPos < 5) { + ++xmlnsPos; + if(xmlnsPos == 1) { if(ch != 'm') startsWithXmlns = false; } + else if(xmlnsPos == 2) { if(ch != 'l') startsWithXmlns = false; } + else if(xmlnsPos == 3) { if(ch != 'n') startsWithXmlns = false; } + else if(xmlnsPos == 4) { if(ch != 's') startsWithXmlns = false; } + else if(xmlnsPos == 5) { + if(ch != ':') throw new XmlPullParserException( + "after xmlns in attribute name must be colon" + +"when namespaces are enabled", this, null); + //colonPos = pos - 1 + bufAbsoluteStart; + } + } + if(ch == ':') { + if(colonPos != -1) throw new XmlPullParserException( + "only one colon is allowed in attribute name" + +" when namespaces are enabled", this, null); + colonPos = pos - 1 + bufAbsoluteStart; + } + } + ch = more(); + } + + ensureAttributesCapacity(attributeCount); + + String name = null; + String prefix = null; + if(processNamespaces) { + if(xmlnsPos < 4) startsWithXmlns = false; + if(startsWithXmlns) { + if(colonPos != -1) { + //prefix = attributePrefix[ attributeCount ] = null; + final int nameLen = pos - 2 - (colonPos - bufAbsoluteStart); + if(nameLen == 0) { + throw new XmlPullParserException( + "namespace prefix is required after xmlns: " + +" when namespaces are enabled", this, null); + } + name = //attributeName[ attributeCount ] = + newString(buf, colonPos - bufAbsoluteStart + 1, nameLen); + } + } else { + if(colonPos != -1) { + int prefixLen = colonPos - nameStart; + prefix = attributePrefix[ attributeCount ] = + newString(buf, nameStart - bufAbsoluteStart,prefixLen); + //colonPos - (nameStart - bufAbsoluteStart)); + int nameLen = pos - 2 - (colonPos - bufAbsoluteStart); + name = attributeName[ attributeCount ] = + newString(buf, colonPos - bufAbsoluteStart + 1, nameLen); + } else { + prefix = attributePrefix[ attributeCount ] = null; + name = attributeName[ attributeCount ] = + newString(buf, nameStart - bufAbsoluteStart, + pos - 1 - (nameStart - bufAbsoluteStart)); + } + if(!allStringsInterned) { + attributeNameHash[ attributeCount ] = name.hashCode(); + } + } + + } else { + name = attributeName[ attributeCount ] = + newString(buf, nameStart - bufAbsoluteStart, + pos - 1 - (nameStart - bufAbsoluteStart)); + if(!allStringsInterned) { + attributeNameHash[ attributeCount ] = name.hashCode(); + } + } + + while(isS(ch)) { + ch = more(); + } // skip additional spaces + if(ch != '=') { + throw new XmlPullParserException( + "expected = after attribute name", this, null); + } + ch = more(); + while(isS(ch)) { + ch = more(); + } // skip additional spaces + + final char delimit = ch; + if(delimit != '"' && delimit != '\'') throw new XmlPullParserException( + "attribute value must start with quotation or apostrophe not " + +printable(delimit), this, null); + boolean normalizedCR = false; + usePC = false; + pcStart = pcEnd; + posStart = pos; + + while(true) { + ch = more(); + if(ch == delimit) { + break; + } if(ch == '<') { + throw new XmlPullParserException( + "markup not allowed inside attribute value - illegal < ", this, null); + } if(ch == '&') { + posEnd = pos - 1; + if(!usePC) { + final boolean hadCharData = posEnd > posStart; + if(hadCharData) { + // posEnd is already set correctly!!! + joinPC(); + } else { + usePC = true; + pcStart = pcEnd = 0; + } + } + final char[] resolvedEntity = parseEntityRef(); + if(resolvedEntity == null) { + if(entityRefName == null) { + entityRefName = newString(buf, posStart, posEnd - posStart); + } + throw new XmlPullParserException( + "could not resolve entity named '"+printable(entityRefName)+"'", + this, null); + } + for (int i = 0; i < resolvedEntity.length; i++) + { + if(pcEnd >= pc.length) ensurePC(pcEnd); + pc[pcEnd++] = resolvedEntity[ i ]; + } + } else if(ch == '\t' || ch == '\n' || ch == '\r') { + if(!usePC) { + posEnd = pos - 1; + if(posEnd > posStart) { + joinPC(); + } else { + usePC = true; + pcEnd = pcStart = 0; + } + } + //assert usePC == true; + if(pcEnd >= pc.length) ensurePC(pcEnd); + if(ch != '\n' || !normalizedCR) { + pc[pcEnd++] = ' '; //'\n'; + } + + } else { + if(usePC) { + if(pcEnd >= pc.length) ensurePC(pcEnd); + pc[pcEnd++] = ch; + } + } + normalizedCR = ch == '\r'; + } + + + if(processNamespaces && startsWithXmlns) { + String ns = null; + if(!usePC) { + ns = newStringIntern(buf, posStart, pos - 1 - posStart); + } else { + ns = newStringIntern(pc, pcStart, pcEnd - pcStart); + } + ensureNamespacesCapacity(namespaceEnd); + int prefixHash = -1; + if(colonPos != -1) { + if(ns.length() == 0) { + throw new XmlPullParserException( + "non-default namespace can not be declared to be empty string", this, null); + } + // declare new namespace + namespacePrefix[ namespaceEnd ] = name; + if(!allStringsInterned) { + prefixHash = namespacePrefixHash[ namespaceEnd ] = name.hashCode(); + } + } else { + // declare new default namespace ... + namespacePrefix[ namespaceEnd ] = null; + if(!allStringsInterned) { + prefixHash = namespacePrefixHash[ namespaceEnd ] = -1; + } + } + namespaceUri[ namespaceEnd ] = ns; + final int startNs = elNamespaceCount[ depth - 1 ]; + for (int i = namespaceEnd - 1; i >= startNs; --i) + { + if(((allStringsInterned || name == null) && namespacePrefix[ i ] == name) + || (!allStringsInterned && name != null && + namespacePrefixHash[ i ] == prefixHash + && name.equals(namespacePrefix[ i ]) + )) + { + final String s = name == null ? "default" : "'"+name+"'"; + throw new XmlPullParserException( + "duplicated namespace declaration for "+s+" prefix", this, null); + } + } + + ++namespaceEnd; + + } else { + if(!usePC) { + attributeValue[ attributeCount ] = + new String(buf, posStart, pos - 1 - posStart); + } else { + attributeValue[ attributeCount ] = + new String(pc, pcStart, pcEnd - pcStart); + } + ++attributeCount; + } + posStart = prevPosStart - bufAbsoluteStart; + return ch; + } + + protected char[] charRefOneCharBuf = new char[1]; + + protected char[] parseEntityRef() + throws XmlPullParserException, IOException + { + entityRefName = null; + posStart = pos; + char ch = more(); + if(ch == '#') { + // parse character reference + char charRef = 0; + ch = more(); + if(ch == 'x') { + //encoded in hex + while(true) { + ch = more(); + if(ch >= '0' && ch <= '9') { + charRef = (char)(charRef * 16 + (ch - '0')); + } else if(ch >= 'a' && ch <= 'f') { + charRef = (char)(charRef * 16 + (ch - ('a' - 10))); + } else if(ch >= 'A' && ch <= 'F') { + charRef = (char)(charRef * 16 + (ch - ('A' - 10))); + } else if(ch == ';') { + break; + } else { + throw new XmlPullParserException( + "character reference (with hex value) may not contain " + +printable(ch), this, null); + } + } + } else { + // encoded in decimal + while(true) { + if(ch >= '0' && ch <= '9') { + charRef = (char)(charRef * 10 + (ch - '0')); + } else if(ch == ';') { + break; + } else { + throw new XmlPullParserException( + "character reference (with decimal value) may not contain " + +printable(ch), this, null); + } + ch = more(); + } + } + posEnd = pos - 1; + charRefOneCharBuf[0] = charRef; + if(tokenize) { + text = newString(charRefOneCharBuf, 0, 1); + } + return charRefOneCharBuf; + } else { + // [68] EntityRef ::= '&' Name ';' + // scan name until ; + if(!isNameStartChar(ch)) { + throw new XmlPullParserException( + "entity reference names can not start with character '" + +printable(ch)+"'", this, null); + } + while(true) { + ch = more(); + if(ch == ';') { + break; + } + if(!isNameChar(ch)) { + throw new XmlPullParserException( + "entity reference name can not contain character " + +printable(ch)+"'", this, null); + } + } + posEnd = pos - 1; + // determine what name maps to + final int len = posEnd - posStart; + if(len == 2 && buf[posStart] == 'l' && buf[posStart+1] == 't') { + if(tokenize) { + text = "<"; + } + charRefOneCharBuf[0] = '<'; + return charRefOneCharBuf; + } else if(len == 3 && buf[posStart] == 'a' + && buf[posStart+1] == 'm' && buf[posStart+2] == 'p') { + if(tokenize) { + text = "&"; + } + charRefOneCharBuf[0] = '&'; + return charRefOneCharBuf; + } else if(len == 2 && buf[posStart] == 'g' && buf[posStart+1] == 't') { + if(tokenize) { + text = ">"; + } + charRefOneCharBuf[0] = '>'; + return charRefOneCharBuf; + } else if(len == 4 && buf[posStart] == 'a' && buf[posStart+1] == 'p' + && buf[posStart+2] == 'o' && buf[posStart+3] == 's') + { + if(tokenize) { + text = "'"; + } + charRefOneCharBuf[0] = '\''; + return charRefOneCharBuf; + } else if(len == 4 && buf[posStart] == 'q' && buf[posStart+1] == 'u' + && buf[posStart+2] == 'o' && buf[posStart+3] == 't') + { + if(tokenize) { + text = "\""; + } + charRefOneCharBuf[0] = '"'; + return charRefOneCharBuf; + } else { + final char[] result = lookuEntityReplacement(len); + if(result != null) { + return result; + } + } + if(tokenize) text = null; + return null; + } + } + + protected char[] lookuEntityReplacement(int entitNameLen) + throws XmlPullParserException, IOException + + { + if(!allStringsInterned) { + final int hash = fastHash(buf, posStart, posEnd - posStart); + LOOP: + for (int i = entityEnd - 1; i >= 0; --i) + { + if(hash == entityNameHash[ i ] && entitNameLen == entityNameBuf[ i ].length) { + final char[] entityBuf = entityNameBuf[ i ]; + for (int j = 0; j < entitNameLen; j++) + { + if(buf[posStart + j] != entityBuf[j]) continue LOOP; + } + if(tokenize) text = entityReplacement[ i ]; + return entityReplacementBuf[ i ]; + } + } + } else { + entityRefName = newString(buf, posStart, posEnd - posStart); + for (int i = entityEnd - 1; i >= 0; --i) + { + // take advantage that interning for newStirng is enforced + if(entityRefName == entityName[ i ]) { + if(tokenize) text = entityReplacement[ i ]; + return entityReplacementBuf[ i ]; + } + } + } + return null; + } + + + protected void parseComment() + throws XmlPullParserException, IOException + { + // implements XML 1.0 Section 2.5 Comments + + //ASSUMPTION: seen + ch = more(); + if(seenDashDash && ch != '>') { + throw new XmlPullParserException( + "in comment after two dashes (--) next character must be >" + +" not "+printable(ch), this, null); + } + if(ch == '-') { + if(!seenDash) { + seenDash = true; + } else { + seenDashDash = true; + seenDash = false; + } + } else if(ch == '>') { + if(seenDashDash) { + break; // found end sequence!!!! + } else { + seenDashDash = false; + } + seenDash = false; + } else { + seenDash = false; + } + if(normalizeIgnorableWS) { + if(ch == '\r') { + normalizedCR = true; + //posEnd = pos -1; + //joinPC(); + // posEnd is already set + if(!usePC) { + posEnd = pos -1; + if(posEnd > posStart) { + joinPC(); + } else { + usePC = true; + pcStart = pcEnd = 0; + } + } + //assert usePC == true; + if(pcEnd >= pc.length) ensurePC(pcEnd); + pc[pcEnd++] = '\n'; + } else if(ch == '\n') { + if(!normalizedCR && usePC) { + if(pcEnd >= pc.length) ensurePC(pcEnd); + pc[pcEnd++] = '\n'; + } + normalizedCR = false; + } else { + if(usePC) { + if(pcEnd >= pc.length) ensurePC(pcEnd); + pc[pcEnd++] = ch; + } + normalizedCR = false; + } + } + } + + } catch(EOFException ex) { + throw new XmlPullParserException( + "comment started on line "+curLine+" and column "+curColumn+" was not closed", + this, ex); + } + if(tokenize) { + posEnd = pos - 3; + if(usePC) { + pcEnd -= 2; + } + } + } + + protected boolean parsePI() + throws XmlPullParserException, IOException + { + if(tokenize) posStart = pos; + final int curLine = lineNumber; + final int curColumn = columnNumber; + int piTargetStart = pos + bufAbsoluteStart; + int piTargetEnd = -1; + final boolean normalizeIgnorableWS = tokenize == true && roundtripSupported == false; + boolean normalizedCR = false; + + try { + boolean seenQ = false; + char ch = more(); + if(isS(ch)) { + throw new XmlPullParserException( + "processing instruction PITarget must be exactly after + //ch = more(); + + if(ch == '?') { + seenQ = true; + } else if(ch == '>') { + if(seenQ) { + break; // found end sequence!!!! + } + seenQ = false; + } else { + if(piTargetEnd == -1 && isS(ch)) { + piTargetEnd = pos - 1 + bufAbsoluteStart; + + // [17] PITarget ::= Name - (('X' | 'x') ('M' | 'm') ('L' | 'l')) + if((piTargetEnd - piTargetStart) == 3) { + if((buf[piTargetStart] == 'x' || buf[piTargetStart] == 'X') + && (buf[piTargetStart+1] == 'm' || buf[piTargetStart+1] == 'M') + && (buf[piTargetStart+2] == 'l' || buf[piTargetStart+2] == 'L') + ) + { + if(piTargetStart > 3) { // posStart) { + joinPC(); + } else { + usePC = true; + pcStart = pcEnd = 0; + } + } + //assert usePC == true; + if(pcEnd >= pc.length) ensurePC(pcEnd); + pc[pcEnd++] = '\n'; + } else if(ch == '\n') { + if(!normalizedCR && usePC) { + if(pcEnd >= pc.length) ensurePC(pcEnd); + pc[pcEnd++] = '\n'; + } + normalizedCR = false; + } else { + if(usePC) { + if(pcEnd >= pc.length) ensurePC(pcEnd); + pc[pcEnd++] = ch; + } + normalizedCR = false; + } + } + ch = more(); + } + } catch(EOFException ex) { + throw new XmlPullParserException( + "processing instruction started on line "+curLine+" and column "+curColumn + +" was not closed", + this, ex); + } + if(piTargetEnd == -1) { + piTargetEnd = pos - 2 + bufAbsoluteStart; + } + piTargetStart -= bufAbsoluteStart; + piTargetEnd -= bufAbsoluteStart; + if(tokenize) { + posEnd = pos - 2; + if(normalizeIgnorableWS) { + --pcEnd; + } + } + return true; + } + + protected final static char[] VERSION = "version".toCharArray(); + protected final static char[] NCODING = "ncoding".toCharArray(); + protected final static char[] TANDALONE = "tandalone".toCharArray(); + protected final static char[] YES = "yes".toCharArray(); + protected final static char[] NO = "no".toCharArray(); + + + + protected void parseXmlDecl(char ch) + throws XmlPullParserException, IOException + { + preventBufferCompaction = true; + bufStart = 0; // necessary to keep pos unchanged during expansion! + + ch = skipS(ch); + ch = requireInput(ch, VERSION); + ch = skipS(ch); + if(ch != '=') { + throw new XmlPullParserException( + "expected equals sign (=) after version and not "+printable(ch), this, null); + } + ch = more(); + ch = skipS(ch); + if(ch != '\'' && ch != '"') { + throw new XmlPullParserException( + "expected apostrophe (') or quotation mark (\") after version and not " + +printable(ch), this, null); + } + final char quotChar = ch; + final int versionStart = pos; + ch = more(); + while(ch != quotChar) { + if((ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z') && (ch < '0' || ch > '9') + && ch != '_' && ch != '.' && ch != ':' && ch != '-') + { + throw new XmlPullParserException( + " 'z') && (ch < 'A' || ch > 'Z')) + { + throw new XmlPullParserException( + " 'z') && (ch < 'A' || ch > 'Z') && (ch < '0' || ch > '9') + && ch != '.' && ch != '_' && ch != '-') + { + throw new XmlPullParserException( + " as last part of ') { + throw new XmlPullParserException( + "expected ?> as last part of ' && bracketLevel == 0) break; + if(normalizeIgnorableWS) { + if(ch == '\r') { + normalizedCR = true; + //posEnd = pos -1; + //joinPC(); + // posEnd is alreadys set + if(!usePC) { + posEnd = pos -1; + if(posEnd > posStart) { + joinPC(); + } else { + usePC = true; + pcStart = pcEnd = 0; + } + } + //assert usePC == true; + if(pcEnd >= pc.length) ensurePC(pcEnd); + pc[pcEnd++] = '\n'; + } else if(ch == '\n') { + if(!normalizedCR && usePC) { + if(pcEnd >= pc.length) ensurePC(pcEnd); + pc[pcEnd++] = '\n'; + } + normalizedCR = false; + } else { + if(usePC) { + if(pcEnd >= pc.length) ensurePC(pcEnd); + pc[pcEnd++] = ch; + } + normalizedCR = false; + } + } + + } + posEnd = pos - 1; + } + + protected void parseCDSect(boolean hadCharData) + throws XmlPullParserException, IOException + { + char ch = more(); + if(ch != 'C') throw new XmlPullParserException( + "expected <[CDATA[ for comment start", this, null); + ch = more(); + if(ch != 'D') throw new XmlPullParserException( + "expected <[CDATA[ for comment start", this, null); + ch = more(); + if(ch != 'A') throw new XmlPullParserException( + "expected <[CDATA[ for comment start", this, null); + ch = more(); + if(ch != 'T') throw new XmlPullParserException( + "expected <[CDATA[ for comment start", this, null); + ch = more(); + if(ch != 'A') throw new XmlPullParserException( + "expected <[CDATA[ for comment start", this, null); + ch = more(); + if(ch != '[') throw new XmlPullParserException( + "expected posStart) { + joinPC(); + } else { + usePC = true; + pcStart = pcEnd = 0; + } + } + } + } + boolean seenBracket = false; + boolean seenBracketBracket = false; + boolean normalizedCR = false; + while(true) { + // scan until it hits "]]>" + ch = more(); + if(ch == ']') { + if(!seenBracket) { + seenBracket = true; + } else { + seenBracketBracket = true; + //seenBracket = false; + } + } else if(ch == '>') { + if(seenBracket && seenBracketBracket) { + break; // found end sequence!!!! + } else { + seenBracketBracket = false; + } + seenBracket = false; + } else { + if(seenBracket) { + seenBracket = false; + } + } + if(normalizeInput) { + // deal with normalization issues ... + if(ch == '\r') { + normalizedCR = true; + posStart = cdStart - bufAbsoluteStart; + posEnd = pos - 1; // posEnd is alreadys set + if(!usePC) { + if(posEnd > posStart) { + joinPC(); + } else { + usePC = true; + pcStart = pcEnd = 0; + } + } + //assert usePC == true; + if(pcEnd >= pc.length) ensurePC(pcEnd); + pc[pcEnd++] = '\n'; + } else if(ch == '\n') { + if(!normalizedCR && usePC) { + if(pcEnd >= pc.length) ensurePC(pcEnd); + pc[pcEnd++] = '\n'; + } + normalizedCR = false; + } else { + if(usePC) { + if(pcEnd >= pc.length) ensurePC(pcEnd); + pc[pcEnd++] = ch; + } + normalizedCR = false; + } + } + } + } catch(EOFException ex) { + throw new XmlPullParserException( + "CDATA section started on line "+curLine+" and column "+curColumn+" was not closed", + this, ex); + } + if(normalizeInput) { + if(usePC) { + pcEnd = pcEnd - 2; + } + } + posStart = cdStart - bufAbsoluteStart; + posEnd = pos - 3; + } + + protected void fillBuf() throws IOException, XmlPullParserException { + if(reader == null) throw new XmlPullParserException( + "reader must be set before parsing is started"); + + if(bufEnd > bufSoftLimit) { + boolean compact = bufStart > bufSoftLimit; + boolean expand = false; + if(preventBufferCompaction) { + compact = false; + expand = true; + } else if(!compact) { + //freeSpace + if(bufStart < buf.length / 2) { + expand = true; + } else { + // at least half of buffer can be reclaimed --> worthwhile effort!!! + compact = true; + } + } + + // if buffer almost full then compact it + if(compact) { + System.arraycopy(buf, bufStart, buf, 0, bufEnd - bufStart); + } else if(expand) { + final int newSize = 2 * buf.length; + final char[] newBuf = new char[ newSize ]; + System.arraycopy(buf, bufStart, newBuf, 0, bufEnd - bufStart); + buf = newBuf; + if(bufLoadFactor > 0) { + bufSoftLimit = (int) (( ((long) bufLoadFactor) * buf.length ) /100); + } + + } else { + throw new XmlPullParserException("internal error in fillBuffer()"); + } + bufEnd -= bufStart; + pos -= bufStart; + posStart -= bufStart; + posEnd -= bufStart; + bufAbsoluteStart += bufStart; + bufStart = 0; + } + final int len = buf.length - bufEnd > READ_CHUNK_SIZE ? READ_CHUNK_SIZE : buf.length - bufEnd; + final int ret = reader.read(buf, bufEnd, len); + if(ret > 0) { + bufEnd += ret; + return; + } + if(ret == -1) { + if(bufAbsoluteStart == 0 && pos == 0) { + throw new EOFException("input contained no data"); + } else { + if(seenRoot && depth == 0) { + reachedEnd = true; + return; + } else { + StringBuffer expectedTagStack = new StringBuffer(); + if(depth > 0) { + expectedTagStack.append(" - expected end tag"); + if(depth > 1) { + expectedTagStack.append("s"); //more than one end tag + } + expectedTagStack.append(" "); + for (int i = depth; i > 0; i--) + { + String tagName = new String(elRawName[i], 0, elRawNameEnd[i]); + expectedTagStack.append("'); + } + expectedTagStack.append(" to close"); + for (int i = depth; i > 0; i--) + { + if(i != depth) { + expectedTagStack.append(" and"); //more than one end tag + } + String tagName = new String(elRawName[i], 0, elRawNameEnd[i]); + expectedTagStack.append(" start tag <"+tagName+">"); + expectedTagStack.append(" from line "+elRawNameLine[i]); + } + expectedTagStack.append(", parser stopped on"); + } + throw new EOFException("no more data available" + +expectedTagStack.toString()+getPositionDescription()); + } + } + } else { + throw new IOException("error reading input, returned "+ret); + } + } + + protected char more() throws IOException, XmlPullParserException { + if(pos >= bufEnd) { + fillBuf(); + if(reachedEnd) { + return (char)-1; + } + } + final char ch = buf[pos++]; + if(ch == '\n') { + ++lineNumber; columnNumber = 1; + } + else { + ++columnNumber; + } + return ch; + } + + protected void ensurePC(int end) { + final int newSize = end > READ_CHUNK_SIZE ? 2 * end : 2 * READ_CHUNK_SIZE; + final char[] newPC = new char[ newSize ]; + System.arraycopy(pc, 0, newPC, 0, pcEnd); + pc = newPC; + } + + protected void joinPC() { + final int len = posEnd - posStart; + final int newEnd = pcEnd + len + 1; + if(newEnd >= pc.length) { + ensurePC(newEnd); + } + System.arraycopy(buf, posStart, pc, pcEnd, len); + pcEnd += len; + usePC = true; + + } + + protected char requireInput(char ch, char[] input) + throws XmlPullParserException, IOException + { + for (int i = 0; i < input.length; i++) + { + if(ch != input[i]) { + throw new XmlPullParserException( + "expected "+printable(input[i])+" in "+new String(input) + +" and not "+printable(ch), this, null); + } + ch = more(); + } + return ch; + } + + protected char requireNextS() + throws XmlPullParserException, IOException + { + final char ch = more(); + if(!isS(ch)) { + throw new XmlPullParserException( + "white space is required and not "+printable(ch), this, null); + } + return skipS(ch); + } + + protected char skipS(char ch) + throws XmlPullParserException, IOException + { + while(isS(ch)) { ch = more(); } // skip additional spaces + return ch; + } + + protected static final int LOOKUP_MAX = 0x400; + protected static final char LOOKUP_MAX_CHAR = (char)LOOKUP_MAX; + protected static boolean[] lookupNameStartChar = new boolean[ LOOKUP_MAX ]; + protected static boolean[] lookupNameChar = new boolean[ LOOKUP_MAX ]; + + private static void setName(char ch) + { + lookupNameChar[ ch ] = true; + } + private static void setNameStart(char ch) + { + lookupNameStartChar[ ch ] = true; setName(ch); + } + + static { + setNameStart(':'); + for (char ch = 'A'; ch <= 'Z'; ++ch) setNameStart(ch); + setNameStart('_'); + for (char ch = 'a'; ch <= 'z'; ++ch) setNameStart(ch); + for (char ch = '\u00c0'; ch <= '\u02FF'; ++ch) setNameStart(ch); + for (char ch = '\u0370'; ch <= '\u037d'; ++ch) setNameStart(ch); + for (char ch = '\u037f'; ch < '\u0400'; ++ch) setNameStart(ch); + + setName('-'); + setName('.'); + for (char ch = '0'; ch <= '9'; ++ch) setName(ch); + setName('\u00b7'); + for (char ch = '\u0300'; ch <= '\u036f'; ++ch) setName(ch); + } + + //private final static boolean isNameStartChar(char ch) { + protected boolean isNameStartChar(char ch) { + return (ch < LOOKUP_MAX_CHAR && lookupNameStartChar[ ch ]) + || (ch >= LOOKUP_MAX_CHAR && ch <= '\u2027') + || (ch >= '\u202A' && ch <= '\u218F') + || (ch >= '\u2800' && ch <= '\uFFEF') ; + + } + + protected boolean isNameChar(char ch) { + return (ch < LOOKUP_MAX_CHAR && lookupNameChar[ ch ]) + || (ch >= LOOKUP_MAX_CHAR && ch <= '\u2027') + || (ch >= '\u202A' && ch <= '\u218F') + || (ch >= '\u2800' && ch <= '\uFFEF'); + } + + protected boolean isS(char ch) { + return (ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t'); + } + + protected String printable(char ch) { + if(ch == '\n') { + return "\\n"; + } else if(ch == '\r') { + return "\\r"; + } else if(ch == '\t') { + return "\\t"; + } else if(ch == '\'') { + return "\\'"; + } if(ch > 127 || ch < 32) { + return "\\u"+Integer.toHexString((int)ch); + } + return ""+ch; + } + + protected String printable(String s) { + if(s == null) return null; + final int sLen = s.length(); + StringBuffer buf = new StringBuffer(sLen + 10); + for(int i = 0; i < sLen; ++i) { + buf.append(printable(s.charAt(i))); + } + s = buf.toString(); + return s; + } +} + + diff --git a/src/main/java/com/reandroid/xml/parser/MXParserCachingStrings.java b/src/main/java/com/reandroid/xml/parser/MXParserCachingStrings.java new file mode 100644 index 0000000..4be5a9d --- /dev/null +++ b/src/main/java/com/reandroid/xml/parser/MXParserCachingStrings.java @@ -0,0 +1,171 @@ +/* + * This class is taken from org.xmlpull.* + * + * Check license: http://xmlpull.org + * + */ + +/*This package is renamed from org.xmlpull.* to avoid conflicts*/ +package com.reandroid.xml.parser; + +public class MXParserCachingStrings extends MXParser implements Cloneable +{ + protected final static boolean CACHE_STATISTICS = false; + protected final static boolean TRACE_SIZING = false; + protected final static int INITIAL_CAPACITY = 13; + protected int cacheStatCalls; + protected int cacheStatWalks; + protected int cacheStatResets; + protected int cacheStatRehash; + + protected static final int CACHE_LOAD = 77; + protected int cacheEntriesCount; + protected int cacheEntriesThreshold; + + protected char[][] keys; + protected String[] values; + + public MXParserCachingStrings() { + super(); + allStringsInterned = true; + initStringCache(); + } + + @Override + public void setFeature(String name, boolean state) throws XmlPullParserException + { + if(FEATURE_NAMES_INTERNED.equals(name)) { + if(eventType != START_DOCUMENT) throw new XmlPullParserException( + "interning names feature can only be changed before parsing", this, null); + allStringsInterned = state; + if(!state && keys != null) { + resetStringCache(); + } + } else { + super.setFeature(name, state); + } + } + + public boolean getFeature(String name) + { + if(FEATURE_NAMES_INTERNED.equals(name)) { + return allStringsInterned; + } else { + return super.getFeature(name); + } + } + + + protected String newString(char[] cbuf, int off, int len) { + if(allStringsInterned) { + return newStringIntern(cbuf, off, len); + } else { + return super.newString(cbuf, off, len); + } + } + + protected String newStringIntern(char[] cbuf, int off, int len) { + if(CACHE_STATISTICS) { + ++cacheStatCalls; + } + if (cacheEntriesCount >= cacheEntriesThreshold) { + rehash(); + } + int offset = fastHash(cbuf, off, len) % keys.length; + char[] k = null; + while( (k = keys[offset]) != null + && !keysAreEqual(k, 0, k.length, + cbuf, off, len)) + { + offset = (offset + 1) % keys.length; + if(CACHE_STATISTICS) ++cacheStatWalks; + } + if (k != null) { + return values[offset]; + } else { + k = new char[len]; + System.arraycopy(cbuf, off, k, 0, len); + final String v = new String(k).intern(); + keys[offset] = k; + values[offset] = v; + ++cacheEntriesCount; + return v; + } + + } + + protected void initStringCache() { + if(keys == null) { + if(INITIAL_CAPACITY < 0) { + throw new IllegalArgumentException("Illegal initial capacity: " + INITIAL_CAPACITY); + } + if(CACHE_LOAD < 0 || CACHE_LOAD > 99) { + throw new IllegalArgumentException("Illegal load factor: " + CACHE_LOAD); + } + cacheEntriesThreshold = (int)((INITIAL_CAPACITY * CACHE_LOAD)/100); + if(cacheEntriesThreshold >= INITIAL_CAPACITY) { + throw new RuntimeException( + "internal error: threshold must be less than capacity: "+INITIAL_CAPACITY); + } + keys = new char[INITIAL_CAPACITY][]; + values = new String[INITIAL_CAPACITY]; + cacheEntriesCount = 0; + } + } + + protected void resetStringCache() { + if(CACHE_STATISTICS) { + ++cacheStatResets; + } + initStringCache(); + } + + private void rehash() { + if(CACHE_STATISTICS) ++cacheStatRehash; + final int newSize = 2 * keys.length + 1; + cacheEntriesThreshold = (int)((newSize * CACHE_LOAD)/100); + if(cacheEntriesThreshold >= newSize) throw new RuntimeException( + "internal error: threshold must be less than capacity: "+newSize); + + final char[][] newKeys = new char[newSize][]; + final String[] newValues = new String[newSize]; + for(int i = 0; i < keys.length; i++) { + final char[] k = keys[i]; + keys[i] = null; + final String v = values[i]; + values[i] = null; + if(k != null) { + int newOffset = fastHash(k, 0, k.length) % newSize; + char[] newk = null; + while((newk = newKeys[newOffset]) != null) { + if(keysAreEqual(newk, 0, newk.length, + k, 0, k.length)) { + throw new RuntimeException("internal cache error: duplicated keys: "+ + new String(newk)+" and "+new String(k)); + } + newOffset = (newOffset + 1) % newSize; + } + + newKeys[newOffset] = k; + newValues[newOffset] = v; + } + } + keys = newKeys; + values = newValues; + } + + private static boolean keysAreEqual (char[] a, int astart, int alength, + char[] b, int bstart, int blength) { + if(alength != blength) { + return false; + } else { + for(int i = 0; i < alength; i++) { + if(a[astart + i] != b[bstart + i]) { + return false; + } + } + return true; + } + } + +} diff --git a/src/main/java/com/reandroid/xml/parser/MXParserNonValidating.java b/src/main/java/com/reandroid/xml/parser/MXParserNonValidating.java new file mode 100644 index 0000000..2141bd3 --- /dev/null +++ b/src/main/java/com/reandroid/xml/parser/MXParserNonValidating.java @@ -0,0 +1,309 @@ +/* + * This class is taken from org.xmlpull.* + * + * Check license: http://xmlpull.org + * + */ + +/*This package is renamed from org.xmlpull.* to avoid conflicts*/ +package com.reandroid.xml.parser; + +import java.io.IOException; + +public class MXParserNonValidating extends MXParserCachingStrings +{ + private boolean processDocDecl; + + public MXParserNonValidating() { + super(); + } + + @Override + public void setFeature(String name, + boolean state) throws XmlPullParserException + { + if(FEATURE_PROCESS_DOCDECL.equals(name)) { + if(eventType != START_DOCUMENT) throw new XmlPullParserException( + "process DOCDECL feature can only be changed before parsing", this, null); + processDocDecl = state; + } else { + super.setFeature(name, state); + } + } + + @Override + public boolean getFeature(String name) + { + if(FEATURE_PROCESS_DOCDECL.equals(name)) { + return processDocDecl; + } else { + return super.getFeature(name); + } + } + + + @Override + protected char more() throws IOException, XmlPullParserException { + return super.more(); + } + + @Override + protected char[] lookuEntityReplacement(int entitNameLen) throws XmlPullParserException, IOException + + { + if(!allStringsInterned) { + final int hash = fastHash(buf, posStart, posEnd - posStart); + LOOP: + for (int i = entityEnd - 1; i >= 0; --i) + { + if(hash == entityNameHash[ i ] && entitNameLen == entityNameBuf[ i ].length) { + final char[] entityBuf = entityNameBuf[ i ]; + for (int j = 0; j < entitNameLen; j++) + { + if(buf[posStart + j] != entityBuf[j]) continue LOOP; + } + if(tokenize) text = entityReplacement[ i ]; + return entityReplacementBuf[ i ]; + } + } + } else { + entityRefName = newString(buf, posStart, posEnd - posStart); + for (int i = entityEnd - 1; i >= 0; --i) + { + // take advantage that interning for newStirng is enforced + if(entityRefName == entityName[ i ]) { + if(tokenize) { + text = entityReplacement[ i ]; + } + return entityReplacementBuf[ i ]; + } + } + } + return null; + } + + + @Override + protected void parseDocdecl() + throws XmlPullParserException, IOException + { + //make sure that tokenize flag is disabled temporarily!!!! + final boolean oldTokenize = tokenize; + try { + //ASSUMPTION: seen ' + ch = requireNextS(); + int nameStart = pos; + ch = readName(ch); + int nameEnd = pos; + ch = skipS(ch); + // [75] ExternalID ::= 'SYSTEM' S SystemLiteral | 'PUBLIC' S PubidLiteral S SystemLiteral + if(ch == 'S' || ch == 'P') { + ch = processExternalId(ch); + ch = skipS(ch); + } + if(ch == '[') { + processInternalSubset(); + } + ch = skipS(ch); + if(ch != '>') { + throw new XmlPullParserException( + "expected > to finish <[DOCTYPE but got "+printable(ch), this, null); + } + posEnd = pos - 1; + } finally { + tokenize = oldTokenize; + } + } + protected char processExternalId(char ch) + throws XmlPullParserException, IOException + { + // [75] ExternalID ::= 'SYSTEM' S SystemLiteral | 'PUBLIC' S PubidLiteral S SystemLiteral + // [11] SystemLiteral ::= ('"' [^"]* '"') | ("'" [^']* "'") + // [12] PubidLiteral ::= '"' PubidChar* '"' | "'" (PubidChar - "'")* "'" + // [13] PubidChar ::= #x20 | #xD | #xA | [a-zA-Z0-9] | [-'()+,./:=?;!*#@$_%] + + //TODO + + return ch; + } + + protected void processInternalSubset() + throws XmlPullParserException, IOException + { + // [28] ... (markupdecl | DeclSep)* ']' // [WFC: External Subset] + // [28a] DeclSep ::= PEReference | S // [WFC: PE Between Declarations] + + // [69] PEReference ::= '%' Name ';' //[WFC: No Recursion] [WFC: In DTD] + while(true) { + char ch = more(); // firs ttime called it will skip initial "[" + if(ch == ']') break; + if(ch == '%') { + processPEReference(); + } else if(isS(ch)) { + ch = skipS(ch); + } else { + processMarkupDecl(ch); + } + } + } + + protected void processPEReference() + throws XmlPullParserException, IOException + { + //TODO + } + protected void processMarkupDecl(char ch) + throws XmlPullParserException, IOException + { + // [29] markupdecl ::= elementdecl | AttlistDecl | EntityDecl | NotationDecl | PI | Comment + // [WFC: PEs in Internal Subset] + + + //BIG SWITCH statement + if(ch != '<') { + throw new XmlPullParserException("expected < for markupdecl in DTD not "+printable(ch), + this, null); + } + ch = more(); + if(ch == '?') { + parsePI(); + } else if(ch == '!') { + ch = more(); + if(ch == '-') { + // note: if(tokenize == false) posStart/End is NOT changed!!!! + parseComment(); + } else { + ch = more(); + if(ch == 'A') { + processAttlistDecl(ch); //A-TTLIST + } else if(ch == 'E') { + ch = more(); + if(ch == 'L') { + processElementDecl(ch); //EL-EMENT + } else if(ch == 'N') { + processEntityDecl(ch); // EN-TITY + } else { + throw new XmlPullParserException( + "expected ELEMENT or ENTITY after ' + //???? [VC: Unique Element Type Declaration] + // [46] contentspec ::= 'EMPTY' | 'ANY' | Mixed | children + // [47] children ::= (choice | seq) ('?' | '*' | '+')? + // [48] cp ::= (Name | choice | seq) ('?' | '*' | '+')? + // [49] choice ::= '(' S? cp ( S? '|' S? cp )+ S? ')' + // [50] seq ::= '(' S? cp ( S? ',' S? cp )* S? ')' + // [51] Mixed ::= '(' S? '#PCDATA' (S? '|' S? Name)* S? ')*' + // | '(' S? '#PCDATA' S? ')' + + //assert ch == 'L' + ch = requireNextS(); + readName(ch); + ch = requireNextS(); + // readContentSpec(ch); + } + + protected void processAttlistDecl(char ch) + throws XmlPullParserException, IOException + { + // [52] AttlistDecl ::= '' + // [53] AttDef ::= S Name S AttType S DefaultDecl + // [54] AttType ::= StringType | TokenizedType | EnumeratedType + // [55] StringType ::= 'CDATA' + // [56] TokenizedType ::= 'ID' | 'IDREF' | 'IDREFS' | 'ENTITY' | 'ENTITIES' | 'NMTOKEN' + // | 'NMTOKENS' + // [57] EnumeratedType ::= NotationType | Enumeration + // [58] NotationType ::= 'NOTATION' S '(' S? Name (S? '|' S? Name)* S? ')' + // [59] Enumeration ::= '(' S? Nmtoken (S? '|' S? Nmtoken)* S? ')' + // [60] DefaultDecl ::= '#REQUIRED' | '#IMPLIED' | (('#FIXED' S)? AttValue) + // [WFC: No < in Attribute Values] + + //assert ch == 'A' + + } + + + protected void processEntityDecl(char ch) + throws XmlPullParserException, IOException + { + + // [70] EntityDecl ::= GEDecl | PEDecl + // [71] GEDecl ::= '' + // [72] PEDecl ::= '' + // [73] EntityDef ::= EntityValue | (ExternalID NDataDecl?) + // [74] PEDef ::= EntityValue | ExternalID + // [75] ExternalID ::= 'SYSTEM' S SystemLiteral | 'PUBLIC' S PubidLiteral S SystemLiteral + + //[9] EntityValue ::= '"' ([^%&"] | PEReference | Reference)* '"' + // | "'" ([^%&'] | PEReference | Reference)* "'" + + //assert ch == 'N' + + } + + protected void processNotationDecl(char ch) + throws XmlPullParserException, IOException + { + + // [82] NotationDecl ::= '' + // [83] PublicID ::= 'PUBLIC' S PubidLiteral + + //assert ch == 'N' + } + + + + protected char readName(char ch) + throws XmlPullParserException, IOException + { + if(isNameStartChar(ch)) { + throw new XmlPullParserException( + "XML name must start with name start character not "+printable(ch), this, null); + } + while(isNameChar(ch)) { + ch = more(); + } + return ch; + } +} \ No newline at end of file diff --git a/src/main/java/com/reandroid/xml/parser/XMLDocumentParser.java b/src/main/java/com/reandroid/xml/parser/XMLDocumentParser.java new file mode 100755 index 0000000..57ca3f7 --- /dev/null +++ b/src/main/java/com/reandroid/xml/parser/XMLDocumentParser.java @@ -0,0 +1,396 @@ + /* + * Copyright (C) 2022 github.com/REAndroid + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.reandroid.xml.parser; + +import com.reandroid.xml.*; + +import java.io.*; +import java.util.ArrayList; +import java.util.List; + +public class XMLDocumentParser { + private final XmlPullParser mParser; + private XMLDocument mResDocument; + private XMLElement mCurrentElement; + private boolean mNameSpaceCreated; + private StringBuilder mCurrentText; + private List mComments; + private int mIndent; + public XMLDocumentParser(XmlPullParser parser){ + this.mParser=parser; + } + public XMLDocumentParser(InputStream in) throws XMLParseException { + this(createParser(in)); + } + public XMLDocumentParser(File file) throws XMLParseException { + this(createParser(file)); + } + public XMLDocumentParser(String text) throws XMLParseException { + this(createParser(text)); + } + + public XMLDocument parse() throws XMLParseException { + try { + XMLDocument document= parseDocument(); + closeFileInputStream(); + return document; + } catch (XmlPullParserException | IOException e) { + XMLParseException ex=new XMLParseException(e.getMessage()); + ex.setStackTrace(e.getStackTrace()); + throw ex; + } + } + private void closeFileInputStream(){ + if(!(mParser instanceof MXParser)){ + return; + } + MXParser mxParser=(MXParser) mParser; + InputStream inputStream = mxParser.getInputStream(); + if(!(inputStream instanceof FileInputStream)){ + return; + } + try { + inputStream.close(); + } catch (IOException ignored) { + } + } + + private XMLDocument parseDocument() throws XmlPullParserException, IOException { + mResDocument=null; + int type; + while ((type=mParser.nextToken()) !=XmlPullParser.END_DOCUMENT){ + event(type); + } + event(XmlPullParser.END_DOCUMENT); + if(mResDocument==null){ + throw new XmlPullParserException("Failed to parse/empty document"); + } + return mResDocument; + } + private void event(int type) { + if (type == XmlPullParser.START_DOCUMENT){ + onStartDocument(); + }else if (type == XmlPullParser.END_DOCUMENT){ + onEndDocument(); + }else if (type == XmlPullParser.START_TAG){ + onStartTag(); + }else if (type == XmlPullParser.END_TAG){ + onEndTag(); + }else if (type == XmlPullParser.TEXT){ + onText(); + }else if (type == XmlPullParser.ENTITY_REF){ + onEntityRef(); + }else if (type == XmlPullParser.COMMENT){ + onComment(); + }else if (type == XmlPullParser.IGNORABLE_WHITESPACE){ + onIgnorableWhiteSpace(); + }else { + onUnknownType(type); + } + } + private void onStartDocument(){ + mResDocument=new XMLDocument(); + mIndent=-1; + } + private void onEndDocument(){ + flushComments(null); + applyIndent(mResDocument); + } + private void onStartTag(){ + String name=mParser.getName(); + flushTextContent(); + if(mCurrentElement==null){ + if(mResDocument==null){ + onStartDocument(); + } + mCurrentElement=new XMLElement(name); + mResDocument.setDocumentElement(mCurrentElement); + }else { + mCurrentElement=mCurrentElement.createElement(name); + } + checkIndent(); + flushComments(mCurrentElement); + String ns=mParser.getNamespace(); + if(!XMLUtil.isEmpty(ns)){ + String prefix=mParser.getPrefix(); + if(!XMLUtil.isEmpty(prefix)){ + String tagName=appendPrefix(prefix,name); + mCurrentElement.setTagName(tagName); + checkNamespace(prefix, ns); + } + } + loadAttributes(); + } + private void loadAttributes(){ + int max=mParser.getAttributeCount(); + for(int i=0; i(); + } + mComments.add(ce); + } + private void flushComments(XMLElement element){ + if(mComments==null){ + return; + } + if(element!=null){ + element.addComments(mComments); + } + mComments.clear(); + mComments=null; + } + private void onIgnorableWhiteSpace(){ + } + private void onIgnore(int type){ + + } + private void onUnknownType(int type){ + String typeName=toTypeName(type); + //System.err.println("Unknown TYPE = "+typeName+" "+type); + } + private String toTypeName(int type){ + String[] allTypes=XmlPullParser.TYPES; + if(type<0 || type>=allTypes.length){ + return "type:"+type; + } + return allTypes[type]; + } + + private void checkIndent(){ + if(mIndent>=0){ + return; + } + String txt=mParser.getText(); + if(txt==null){ + return; + } + int len=txt.length(); + int col=mParser.getColumnNumber(); + mIndent=col-len; + if(mIndent<0){ + mIndent=0; + } + } + private void applyIndent(XMLDocument resDocument){ + if(mIndent<=0 || mIndent>5 || resDocument==null){ + mIndent=-1; + return; + } + resDocument.setIndent(mIndent); + mIndent=-1; + } + + private static XmlPullParser createParser(String text) throws XMLParseException { + if(text == null){ + throw new XMLParseException("Text is null, failed to create XmlPullParser"); + } + InputStream in = new ByteArrayInputStream(text.getBytes()); + return createParser(in); + } + private static XmlPullParser createParser(File file) throws XMLParseException { + if(file == null){ + throw new XMLParseException("File is null, failed to create XmlPullParser"); + } + if(!file.isFile()){ + throw new XMLParseException("No such file : "+file.getAbsolutePath()); + } + InputStream in; + try { + in=new FileInputStream(file); + return createParser(in); + } catch (FileNotFoundException e) { + throw new XMLParseException(e.getMessage()); + } + } + private static XmlPullParser createParser(InputStream in) throws XMLParseException { + try { + XmlPullParser parser = new MXParserNonValidating(); + parser.setInput(in, null); + return parser; + } catch (XmlPullParserException e) { + throw new XMLParseException(e.getMessage()); + } + } + + private static boolean isAndroid(int id){ + int pkgId=toPackageId(id); + return pkgId>0 && pkgId<=ANDROID_PACKAGE_MAX; + } + private static boolean isResourceId(int id){ + int pkgId=toPackageId(id); + return pkgId>0 && pkgId<128; + } + private static int toPackageId(int id){ + if(id<=0xff){ + return id; + } + return ((id >> 24) & 0xff); + } + private static final int ANDROID_PACKAGE_MAX=3; +} diff --git a/src/main/java/com/reandroid/xml/parser/XMLParseException.java b/src/main/java/com/reandroid/xml/parser/XMLParseException.java new file mode 100755 index 0000000..bb8cbbf --- /dev/null +++ b/src/main/java/com/reandroid/xml/parser/XMLParseException.java @@ -0,0 +1,24 @@ + /* + * Copyright (C) 2022 github.com/REAndroid + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.reandroid.xml.parser; + +import com.reandroid.xml.XMLException; + +public class XMLParseException extends XMLException { + public XMLParseException(String msg) { + super(msg); + } +} diff --git a/src/main/java/com/reandroid/xml/parser/XmlPullParser.java b/src/main/java/com/reandroid/xml/parser/XmlPullParser.java new file mode 100644 index 0000000..19fc6e0 --- /dev/null +++ b/src/main/java/com/reandroid/xml/parser/XmlPullParser.java @@ -0,0 +1,89 @@ +/* + * This class is taken from org.xmlpull.* + * + * Check license: http://xmlpull.org + * + */ + +/*This package is renamed from org.xmlpull.* to avoid conflicts*/ +package com.reandroid.xml.parser; + +import java.io.InputStream; +import java.io.IOException; +import java.io.Reader; + +public interface XmlPullParser { + + String NO_NAMESPACE = ""; + int START_DOCUMENT = 0; + int END_DOCUMENT = 1; + int START_TAG = 2; + int END_TAG = 3; + int TEXT = 4; + int CDSECT = 5; + int ENTITY_REF = 6; + int IGNORABLE_WHITESPACE = 7; + int PROCESSING_INSTRUCTION = 8; + int COMMENT = 9; + int DOCDECL = 10; + String [] TYPES = { + "START_DOCUMENT", + "END_DOCUMENT", + "START_TAG", + "END_TAG", + "TEXT", + "CDSECT", + "ENTITY_REF", + "IGNORABLE_WHITESPACE", + "PROCESSING_INSTRUCTION", + "COMMENT", + "DOCDECL" + }; + + String FEATURE_PROCESS_NAMESPACES = "http://xmlpull.org/v1/doc/features.html#process-namespaces"; + + String FEATURE_REPORT_NAMESPACE_ATTRIBUTES = "http://xmlpull.org/v1/doc/features.html#report-namespace-prefixes"; + String FEATURE_PROCESS_DOCDECL = "http://xmlpull.org/v1/doc/features.html#process-docdecl"; + + String FEATURE_VALIDATION = "http://xmlpull.org/v1/doc/features.html#validation"; + + void setFeature(String name, boolean state) throws XmlPullParserException; + boolean getFeature(String name); + void setProperty(String name, Object value) throws XmlPullParserException; + Object getProperty(String name); + void setInput(Reader in) throws XmlPullParserException; + void setInput(InputStream inputStream, String inputEncoding) throws XmlPullParserException; + String getInputEncoding(); + void defineEntityReplacementText( String entityName, String replacementText ) throws XmlPullParserException; + int getNamespaceCount(int depth) throws XmlPullParserException; + String getNamespacePrefix(int pos) throws XmlPullParserException; + String getNamespaceUri(int pos) throws XmlPullParserException; + String getNamespace (String prefix); + int getDepth(); + String getPositionDescription(); + int getLineNumber(); + int getColumnNumber(); + boolean isWhitespace() throws XmlPullParserException; + String getText (); + char[] getTextCharacters(int [] holderForStartAndLength); + String getNamespace (); + String getName(); + String getPrefix(); + boolean isEmptyElementTag() throws XmlPullParserException; + int getAttributeCount(); + String getAttributeNamespace (int index); + String getAttributeName (int index); + String getAttributePrefix(int index); + String getAttributeType(int index); + boolean isAttributeDefault(int index); + String getAttributeValue(int index); + String getAttributeValue(String namespace, String name); + int getEventType() throws XmlPullParserException; + int next() throws XmlPullParserException, IOException; + int nextToken() throws XmlPullParserException, IOException; + void require(int type, String namespace, String name) throws XmlPullParserException, IOException; + String nextText() throws XmlPullParserException, IOException; + int nextTag() throws XmlPullParserException, IOException; +// public void skipSubTree() throws XmlPullParserException, IOException; +} + diff --git a/src/main/java/com/reandroid/xml/parser/XmlPullParserException.java b/src/main/java/com/reandroid/xml/parser/XmlPullParserException.java new file mode 100644 index 0000000..11a27a3 --- /dev/null +++ b/src/main/java/com/reandroid/xml/parser/XmlPullParserException.java @@ -0,0 +1,34 @@ +/* + * This class is taken from org.xmlpull.* + * + * Check license: http://xmlpull.org + * + */ + +/*This package is renamed from org.xmlpull.* to avoid conflicts*/ +package com.reandroid.xml.parser; + +public class XmlPullParserException extends Exception { + protected Throwable detail; + protected int row = -1; + protected int column = -1; + + public XmlPullParserException(String s) { + super(s); + } + 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 int getLineNumber() { return row; } + public int getColumnNumber() { return column; } +} +