Skip to content

Commit b942b9b

Browse files
committed
Create XMLPath
1 parent e9243ae commit b942b9b

2 files changed

Lines changed: 594 additions & 0 deletions

File tree

Lines changed: 365 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,365 @@
1+
/*
2+
* Copyright (C) 2022 github.com/REAndroid
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.reandroid.xml;
17+
18+
import com.reandroid.utils.ObjectsUtil;
19+
import com.reandroid.utils.collection.CollectionUtil;
20+
import com.reandroid.utils.collection.EmptyIterator;
21+
import com.reandroid.utils.collection.FilterIterator;
22+
import com.reandroid.utils.collection.IterableIterator;
23+
import com.reandroid.utils.collection.SingleIterator;
24+
import com.reandroid.xml.base.Attribute;
25+
import com.reandroid.xml.base.Document;
26+
import com.reandroid.xml.base.Element;
27+
import com.reandroid.xml.base.NamedNode;
28+
import com.reandroid.xml.base.Node;
29+
30+
import java.util.Iterator;
31+
import java.util.List;
32+
import java.util.function.Predicate;
33+
34+
public class XMLPath implements Predicate<NamedNode> {
35+
36+
private static final int TYPE_UNKNOWN = ObjectsUtil.of(-1);
37+
public static final int TYPE_ELEMENT = ObjectsUtil.of(0);
38+
public static final int TYPE_ATTRIBUTE = ObjectsUtil.of(1);
39+
public static final String ANY_NAME = ObjectsUtil.of("*");
40+
41+
private final XMLPath parent;
42+
private final String name;
43+
private final int nameId;
44+
private final int type;
45+
46+
private XMLPath(XMLPath parent, String name, int nameId, int type) throws InvalidPathException {
47+
this.parent = parent;
48+
this.name = name;
49+
this.nameId = nameId;
50+
this.type = type;
51+
if (parent != null && parent.type() == TYPE_ATTRIBUTE) {
52+
throw new InvalidPathException("Attribute can not have child");
53+
}
54+
if (parent == null && type == TYPE_ATTRIBUTE) {
55+
throw new InvalidPathException("Null parent for attribute");
56+
}
57+
}
58+
private XMLPath(XMLPath parent, String name) throws InvalidPathException {
59+
int type = TYPE_ATTRIBUTE;
60+
int i = name.lastIndexOf(';');
61+
if (i < 0) {
62+
i = name.lastIndexOf('/');
63+
type = TYPE_ELEMENT;
64+
}
65+
if (i < 0) {
66+
throw new InvalidPathException("Name should start with / or ;");
67+
}
68+
this.type = type;
69+
String simpleName = name.substring(i + 1);
70+
this.name = simpleName;
71+
this.nameId = decodeNameId(simpleName);
72+
XMLPath thisPrent;
73+
if (i > 0) {
74+
thisPrent = new XMLPath(parent, name.substring(0, i));
75+
} else {
76+
thisPrent = parent;
77+
}
78+
this.parent = thisPrent;
79+
if (thisPrent != null && thisPrent.type == TYPE_ATTRIBUTE) {
80+
throw new InvalidPathException("Attribute can not have child");
81+
}
82+
}
83+
private XMLPath(NamedNode node) {
84+
this.name = node.getName();
85+
int type = TYPE_UNKNOWN;
86+
int nameId = 0;
87+
Element<?> parentElement = null;
88+
if (node instanceof Attribute) {
89+
type = TYPE_ATTRIBUTE;
90+
Attribute attribute = (Attribute) node;
91+
parentElement = attribute.getParentNode();
92+
nameId = attribute.getNameId();
93+
} else if (node instanceof Element) {
94+
type = TYPE_ELEMENT;
95+
Node parentNode = ((Element<?>) node).getParentNode();
96+
if (parentNode instanceof Element) {
97+
parentElement = (Element<?>) parentNode;
98+
}
99+
}
100+
XMLPath parentPath = null;
101+
if (parentElement != null) {
102+
parentPath = new XMLPath(parentElement);
103+
}
104+
this.parent = parentPath;
105+
this.type = type;
106+
this.nameId = nameId;
107+
}
108+
109+
public XMLPath getParent() {
110+
return parent;
111+
}
112+
public String getName() {
113+
return name;
114+
}
115+
public int getNameId() {
116+
return nameId;
117+
}
118+
private XMLPath getPath(int depth) {
119+
XMLPath path = this;
120+
int i = 0;
121+
while (i != depth) {
122+
path = path.getParent();
123+
i ++;
124+
}
125+
return path;
126+
}
127+
private int depth() {
128+
XMLPath path = getParent();
129+
int i = 0;
130+
while (path != null) {
131+
path = path.getParent();
132+
i ++;
133+
}
134+
return i;
135+
}
136+
public int type() {
137+
return type;
138+
}
139+
public String getPath() {
140+
XMLPath path = this;
141+
StringBuilder builder = new StringBuilder();
142+
while (path != null) {
143+
int nameId = path.getNameId();
144+
if (nameId != 0) {
145+
builder.insert(0, encodeId(nameId));
146+
} else {
147+
builder.insert(0, path.getName());
148+
}
149+
builder.insert(0, getSeparator(path.type()));
150+
path = path.getParent();
151+
}
152+
return builder.toString();
153+
}
154+
public XMLPath clearNameId() throws InvalidPathException {
155+
return changeNameId(0);
156+
}
157+
public XMLPath changeNameId(int id) throws InvalidPathException {
158+
if (id == getNameId()) {
159+
return this;
160+
}
161+
return new XMLPath(getParent(), getName(), id, type());
162+
}
163+
public XMLPath changeName(String name) throws InvalidPathException {
164+
if (getName().equals(name)) {
165+
return this;
166+
}
167+
validateSimpleName(name);
168+
return new XMLPath(getParent(), name, getNameId(), type());
169+
}
170+
public XMLPath parse(String childPath) throws InvalidPathException {
171+
return new XMLPath(this, childPath);
172+
}
173+
public XMLPath attribute(String name) throws InvalidPathException {
174+
validateSimpleName(name);
175+
return new XMLPath(this, name, 0, TYPE_ATTRIBUTE);
176+
}
177+
public XMLPath attribute(int id) throws InvalidPathException {
178+
return new XMLPath(this, encodeId(id), id, TYPE_ATTRIBUTE);
179+
}
180+
public XMLPath element(String name) throws InvalidPathException {
181+
if (type() == TYPE_ATTRIBUTE) {
182+
throw new InvalidPathException("Attribute can not have child element");
183+
}
184+
if (name == null || name.length() == 0) {
185+
throw new InvalidPathException("Name can not be empty");
186+
}
187+
char c = name.charAt(0);
188+
if (c == ';') {
189+
throw new InvalidPathException("Invalid element name: " + name);
190+
}
191+
if (c != '/') {
192+
name = "/" + name;
193+
}
194+
return new XMLPath(this, name);
195+
}
196+
protected boolean matchesName(NamedNode namedNode) {
197+
if (namedNode == null || type() != typeOf(namedNode)) {
198+
return false;
199+
}
200+
String name = this.getName();
201+
if (ANY_NAME.equals(name)) {
202+
return true;
203+
}
204+
int nameId1 = this.getNameId();
205+
if (nameId1 != 0) {
206+
if (namedNode instanceof Attribute) {
207+
return nameId1 == ((Attribute) namedNode).getNameId();
208+
}
209+
}
210+
return name.equals(namedNode.getName());
211+
}
212+
@Override
213+
public boolean test(NamedNode namedNode) {
214+
int depth = depth();
215+
NamedNode parentNode = namedNode;
216+
for (int i = 0; i <= depth; i++) {
217+
if (!getPath(i).matchesName(parentNode)) {
218+
return false;
219+
}
220+
parentNode = getParentNode(parentNode);
221+
}
222+
return parentNode == null;
223+
}
224+
public boolean contains(Document<?> document) {
225+
return findFirst(document) != null;
226+
}
227+
public<T extends NamedNode> List<T> list(Document<?> document) {
228+
return CollectionUtil.toList(find(document));
229+
}
230+
public<T extends NamedNode> T findFirst(Document<?> document) {
231+
return CollectionUtil.getFirst(find(document));
232+
}
233+
public<T extends NamedNode> Iterator<T> find(Document<?> document) {
234+
if (document == null) {
235+
return EmptyIterator.of();
236+
}
237+
return ObjectsUtil.cast(find(SingleIterator.of(document.getDocumentElement()), depth()));
238+
}
239+
private Iterator<NamedNode> find(Iterator<NamedNode> iterator, int depth) {
240+
XMLPath path = getPath(depth);
241+
iterator = FilterIterator.of(iterator, path::matchesName);
242+
if (depth == 0 || !iterator.hasNext()) {
243+
return iterator;
244+
}
245+
int nextDepth = depth - 1;
246+
boolean attribute = nextDepth == 0 && this.type() == TYPE_ATTRIBUTE;
247+
iterator = new IterableIterator<NamedNode, NamedNode>(iterator) {
248+
@Override
249+
public Iterator<NamedNode> iterator(NamedNode namedNode) {
250+
Element<?> element = (Element<?>) namedNode;
251+
if (attribute) {
252+
return ObjectsUtil.cast(element.getAttributes());
253+
}
254+
return ObjectsUtil.cast(element.iterator(Element.class));
255+
}
256+
};
257+
return this.find(iterator, nextDepth);
258+
}
259+
260+
@Override
261+
public boolean equals(Object obj) {
262+
if (this == obj) {
263+
return true;
264+
}
265+
if (!(obj instanceof XMLPath)) {
266+
return false;
267+
}
268+
XMLPath xmlPath = (XMLPath) obj;
269+
return this.getNameId() == xmlPath.getNameId() &&
270+
this.type() == xmlPath.type() &&
271+
ObjectsUtil.equals(this.getParent(), xmlPath.getParent()) &&
272+
ObjectsUtil.equals(this.getName(), xmlPath.getName());
273+
}
274+
275+
@Override
276+
public int hashCode() {
277+
int hash = 31 + getNameId();
278+
hash = hash * 31 + type();
279+
hash = hash * 31 + ObjectsUtil.hash(parent, name);
280+
return hash;
281+
}
282+
283+
@Override
284+
public String toString() {
285+
return getPath();
286+
}
287+
288+
private static NamedNode getParentNode(NamedNode namedNode) {
289+
if (namedNode instanceof Element) {
290+
Node node = ((Element<?>) namedNode).getParentNode();
291+
if (node instanceof Element) {
292+
return (NamedNode) node;
293+
}
294+
return null;
295+
}
296+
if (namedNode instanceof Attribute) {
297+
return ((Attribute) namedNode).getParentNode();
298+
}
299+
return null;
300+
}
301+
private static int decodeNameId(String simpleName) {
302+
if (simpleName.charAt(0) != '@') {
303+
return 0;
304+
}
305+
simpleName = simpleName.substring(1);
306+
try {
307+
long l = Long.decode(simpleName);
308+
return (int) l;
309+
} catch (NumberFormatException e) {
310+
throw new InvalidPathException("Invalid name id: " + simpleName, e);
311+
}
312+
}
313+
private static String encodeId(int id) {
314+
long l = id & 0xffffffffL;
315+
return "@0x" + Long.toHexString(l);
316+
}
317+
private static int typeOf(NamedNode namedNode) {
318+
if (namedNode instanceof Attribute) {
319+
return TYPE_ATTRIBUTE;
320+
}
321+
if (namedNode instanceof Element) {
322+
return TYPE_ELEMENT;
323+
}
324+
return TYPE_UNKNOWN;
325+
}
326+
private static String getSeparator(int type) {
327+
if (type == TYPE_ATTRIBUTE) {
328+
return ";";
329+
}
330+
return "/";
331+
}
332+
public static XMLPath of(NamedNode node) {
333+
return new XMLPath(node);
334+
}
335+
public static XMLPath compile(String path) throws InvalidPathException {
336+
return new XMLPath(null, path);
337+
}
338+
public static XMLPath newElement(String name) throws InvalidPathException {
339+
validateSimpleName(name);
340+
return new XMLPath(null, name, 0, TYPE_ELEMENT);
341+
}
342+
private static void validateSimpleName(String name) throws InvalidPathException {
343+
if (name == null) {
344+
throw new InvalidPathException("Null name");
345+
}
346+
if (name.length() == 0) {
347+
throw new InvalidPathException("Empty name");
348+
}
349+
if (containsIllegalChars(name)) {
350+
throw new InvalidPathException("Name contains illegal characters: " + name);
351+
}
352+
}
353+
private static boolean containsIllegalChars(String simpleName) {
354+
return simpleName.indexOf('/') >= 0 || simpleName.indexOf(';') >= 0;
355+
}
356+
357+
public static class InvalidPathException extends RuntimeException {
358+
public InvalidPathException(String message) {
359+
super(message);
360+
}
361+
public InvalidPathException(String message, Throwable cause) {
362+
super(message, cause);
363+
}
364+
}
365+
}

0 commit comments

Comments
 (0)