1 package uk.ac.cam.ch.wwmm.opsin;
2 
3 import java.util.ArrayDeque;
4 import java.util.ArrayList;
5 import java.util.Deque;
6 import java.util.HashSet;
7 import java.util.List;
8 import java.util.Locale;
9 import java.util.Set;
10 import java.util.regex.Pattern;
11 
12 import static uk.ac.cam.ch.wwmm.opsin.XmlDeclarations.*;
13 
14 /**
15  * A set of useful methods and constants to assist OPSIN
16  * @author dl387
17  *
18  */
19 class OpsinTools {
20 
21 	static final Pattern MATCH_DIGITS = Pattern.compile("\\d+");
22 	static final Pattern MATCH_COLONORSEMICOLON = Pattern.compile("[:;]");
23 
24 	static final Pattern MATCH_AMINOACID_STYLE_LOCANT =Pattern.compile("([A-Z][a-z]?)('*)((\\d+[a-z]?|alpha|beta|gamma|delta|epsilon|zeta|eta|omega)'*)");
25 	static final Pattern MATCH_ELEMENT_SYMBOL =Pattern.compile("[A-Z][a-z]?");
26 	static final Pattern MATCH_ELEMENT_SYMBOL_LOCANT =Pattern.compile("[A-Z][a-z]?'*");
27 	static final Pattern MATCH_NUMERIC_LOCANT =Pattern.compile("(\\d+)[a-z]?'*");
28 	static final char END_OF_SUBSTITUENT = '\u00e9';
29 	static final char END_OF_MAINGROUP = '\u00e2';
30 	static final char END_OF_FUNCTIONALTERM = '\u00FB';
31 
32 	static final String NEWLINE = System.getProperty("line.separator");
33 
34 
isBiochemical(String type, String subType)35 	static boolean isBiochemical(String type, String subType) {
36 		return BIOCHEMICAL_SUBTYPE_VAL.equals(subType) ||
37 				CARBOHYDRATE_TYPE_VAL.equals(type) ||
38 				AMINOACID_TYPE_VAL.equals(type);
39 	}
40 
41 	/**
42 	 * Returns the next sibling suffix node which is not related to altering charge (ium/ide/id)
43 	 * @param currentEl
44 	 */
getNextNonChargeSuffix(Element currentEl)45 	static Element getNextNonChargeSuffix(Element currentEl) {
46 		Element next = getNextSibling(currentEl);
47 		while (next != null) {
48 			if (next.getName().equals(SUFFIX_EL) && !CHARGE_TYPE_VAL.equals(next.getAttributeValue(TYPE_ATR))){
49 				return next;
50 			}
51 			next = getNextSibling(next);
52 		}
53 		return null;
54 	}
55 
56 	/**
57 	 * Returns a new list containing the elements of list1 followed by list2
58 	 * @param list1
59 	 * @param list2
60 	 * @return The new list
61 	 */
combineElementLists(List<Element> list1, List<Element> list2)62 	static List<Element> combineElementLists(List<Element> list1, List<Element> list2) {
63 		List<Element> elementList = new ArrayList<Element>(list1.size() + list2.size());
64 		elementList.addAll(list1);
65 		elementList.addAll(list2);
66 		return elementList;
67 	}
68 
fixLocantCapitalisation(String locant)69 	static String fixLocantCapitalisation(String locant) {
70 		int len = locant.length();
71 		if (len >= 2) {
72 			char lastChar = locant.charAt(len - 1);
73 			if ((lastChar >= 'A' && lastChar <= 'G') && MATCH_DIGITS.matcher(locant).region(0, len - 1).matches() ) {
74 				//erroneous uppercase e.g. 1A rather than 1a
75 				locant = locant.toLowerCase(Locale.ROOT);
76 			}
77 		}
78 		return locant;
79 	}
80 
81 	/**
82 	 * Returns the previous group. This group element need not be a sibling
83 	 * @param current: starting element
84 	 * @return
85 	 */
getPreviousGroup(Element current)86 	static Element getPreviousGroup(Element current) {
87 	  if (current.getName().equals(GROUP_EL)) {//can start with a group or the sub/root the group is in
88 		  current = current.getParent();
89 	  }
90 	  Element parent = current.getParent();
91 	  if (parent == null || parent.getName().equals(WORDRULE_EL)) {
92 		  return null;
93 	  }
94 	  int index = parent.indexOf(current);
95 	  if (index ==0) {
96 		  return getPreviousGroup(parent);//no group found
97 	  }
98 	  Element previous = parent.getChild(index - 1);
99 	  while (previous.getChildCount() != 0) {
100 		  previous = previous.getChild(previous.getChildCount() - 1);
101 	  }
102 	  List<Element> groups = previous.getParent().getChildElements(GROUP_EL);
103 	  if (groups.size() == 0){
104 		  return getPreviousGroup(previous);
105 	  }
106 	  else{
107 		  return groups.get(groups.size() - 1);//return last group if multiple exist e.g. fused ring
108 	  }
109 	}
110 
111 	/**
112 	 * Returns the next group. This group element need not be a sibling
113 	 * @param current: starting element
114 	 * @return
115 	 */
getNextGroup(Element current)116 	static Element getNextGroup(Element current) {
117 	  if (current.getName().equals(GROUP_EL)) {//can start with a group or the sub/root the group is in
118 		  current = current.getParent();
119 	  }
120 	  Element parent = current.getParent();
121 	  if (parent == null || parent.getName().equals(MOLECULE_EL)) {
122 		  return null;
123 	  }
124 	  int index = parent.indexOf(current);
125 	  if (index == parent.getChildCount() - 1) {
126 		  return getNextGroup(parent);//no group found
127 	  }
128 	  Element next = parent.getChild(index + 1);
129 	  while (next.getChildCount() != 0){
130 		  next = next.getChild(0);
131 	  }
132 	  List<Element> groups = next.getParent().getChildElements(GROUP_EL);
133 	  if (groups.size() == 0){
134 		  return getNextGroup(next);
135 	  }
136 	  else{
137 		  return groups.get(0);//return first group if multiple exist e.g. fused ring
138 	  }
139 	}
140 
141 	/**
142 	 * Finds the wordRule element that encloses the given element.
143 	 * Returns the wordRule element or throws an exception
144 	 * @param el
145 	 * @return wordRule Element
146 	 */
getParentWordRule(Element el)147 	static Element getParentWordRule(Element el) {
148 		Element parent = el.getParent();
149 		while(parent != null && !parent.getName().equals(WORDRULE_EL)){
150 			parent = parent.getParent();
151 		}
152 		if (parent == null){
153 			throw new RuntimeException("Cannot find enclosing wordRule element");
154 		}
155 		else{
156 			return parent;
157 		}
158 	}
159 
160 	/**
161 	 * Searches in a depth-first manner for a non-suffix atom that has the target non element symbol locant
162 	 * Returns either that atom or null if one cannot be found
163 	 * @param startingAtom
164 	 * @param targetLocant
165 	 * @return the matching atom or null
166 	 */
depthFirstSearchForNonSuffixAtomWithLocant(Atom startingAtom, String targetLocant)167 	static Atom depthFirstSearchForNonSuffixAtomWithLocant(Atom startingAtom, String targetLocant) {
168 		Deque<Atom> stack = new ArrayDeque<Atom>();
169 		stack.add(startingAtom);
170 		Set<Atom> atomsVisited = new HashSet<Atom>();
171 		while (stack.size() > 0) {
172 			Atom currentAtom = stack.removeLast();
173 			atomsVisited.add(currentAtom);
174 			List<Atom> neighbours = currentAtom.getAtomNeighbours();
175 			for (Atom neighbour : neighbours) {
176 				if (atomsVisited.contains(neighbour)){//already visited
177 					continue;
178 				}
179 				List<String> locants = new ArrayList<String>(neighbour.getLocants());
180 				locants.removeAll(neighbour.getElementSymbolLocants());
181 
182 				//A main group atom, would expect to only find one except in something strange like succinimide
183 				//The locants.size() > 0 condition allows things like terephthalate to work which have an atom between the suffixes and main atoms that has no locant
184 				if (locants.size() > 0 && !neighbour.getType().equals(SUFFIX_TYPE_VAL)){
185 					if (locants.contains(targetLocant)){
186 						return neighbour;
187 					}
188 					continue;
189 				}
190 				stack.add(neighbour);
191 			}
192 		}
193 		return null;
194 	}
195 
196 	/**
197 	 * Searches in a depth-first manner for an atom with a numeric locant
198 	 * Returns either that atom or null if one cannot be found
199 	 * @param startingAtom
200 	 * @return the matching atom or null
201 	 */
depthFirstSearchForAtomWithNumericLocant(Atom startingAtom)202 	static Atom depthFirstSearchForAtomWithNumericLocant(Atom startingAtom){
203 		Deque<Atom> stack = new ArrayDeque<Atom>();
204 		stack.add(startingAtom);
205 		Set<Atom> atomsVisited = new HashSet<Atom>();
206 		while (stack.size() > 0) {
207 			Atom currentAtom = stack.removeLast();
208 			atomsVisited.add(currentAtom);
209 			List<Atom> neighbours = currentAtom.getAtomNeighbours();
210 			for (Atom neighbour : neighbours) {
211 				if (atomsVisited.contains(neighbour)){//already visited
212 					continue;
213 				}
214 				List<String> locants = neighbour.getLocants();
215 				for (String neighbourLocant : locants) {
216 					if (MATCH_NUMERIC_LOCANT.matcher(neighbourLocant).matches()){
217 						return neighbour;
218 					}
219 				}
220 				stack.add(neighbour);
221 			}
222 		}
223 		return null;
224 	}
225 
226 	/**
227 	 * Given a list of annotations returns the word type as indicated by the final annotation of the list
228 	 * @param annotations
229 	 * @return WordType
230 	 * @throws ParsingException
231 	 */
determineWordType(List<Character> annotations)232 	static WordType determineWordType(List<Character> annotations) throws ParsingException {
233 		char finalAnnotation = annotations.get(annotations.size() - 1);
234 		if (finalAnnotation == END_OF_MAINGROUP) {
235 			return WordType.full;
236 		}
237 		else if (finalAnnotation == END_OF_SUBSTITUENT) {
238 			return WordType.substituent;
239 		}
240 		else if (finalAnnotation == END_OF_FUNCTIONALTERM) {
241 			return WordType.functionalTerm;
242 		}
243 		else{
244 			throw new ParsingException("OPSIN bug: Unable to determine word type!");
245 		}
246 
247 	}
248 
249 	/**Gets the next sibling of a given element.
250 	*
251 	* @param element The reference element.
252 	* @return The next Sibling, or null.
253 	*/
getNextSibling(Element element)254 	static Element getNextSibling(Element element) {
255 		Element parent = element.getParent();
256 		int i = parent.indexOf(element);
257 		if (i + 1 >= parent.getChildCount()) {
258 			return null;
259 		}
260 		return parent.getChild(i + 1);
261 	}
262 
263 	/**Gets the first next sibling of a given element whose element name matches the given string.
264 	*
265 	* @param current The reference element.
266 	* @param elName The element name to look for
267 	* @return The matched next Sibling, or null.
268 	*/
getNextSibling(Element current, String elName)269 	static Element getNextSibling(Element current, String elName) {
270 		Element next = getNextSibling(current);
271 		while (next != null) {
272 			if (next.getName().equals(elName)){
273 				return next;
274 			}
275 			next = getNextSibling(next);
276 		}
277 		return null;
278 	}
279 
280 	/**Gets the previous sibling of a given element.
281 	*
282 	* @param element The reference element.
283 	* @return The previous Sibling, or null.
284 	*/
getPreviousSibling(Element element)285 	static Element getPreviousSibling(Element element) {
286 		Element parent = element.getParent();
287 		int i = parent.indexOf(element);
288 		if (i == 0) {
289 			return null;
290 		}
291 		return parent.getChild(i - 1);
292 	}
293 
294 	/**Gets the first previous sibling of a given element whose element name matches the given string.
295 	*
296 	* @param current The reference element.
297 	* @param elName The element name of a element to look for
298 	* @return The matched previous Sibling, or null.
299 	*/
getPreviousSibling(Element current, String elName)300 	static Element getPreviousSibling(Element current, String elName) {
301 		Element prev = getPreviousSibling(current);
302 		while (prev != null) {
303 			if (prev.getName().equals(elName)){
304 				return prev;
305 			}
306 			prev = getPreviousSibling(prev);
307 		}
308 		return null;
309 	}
310 
311 	/**Inserts a element so that it occurs before a reference element. The new element
312 	 * must not currently have a parent.
313 	 *
314 	 * @param element The reference element.
315 	 * @param newElement The new element to insert.
316 	 */
insertBefore(Element element, Element newElement)317 	static void insertBefore(Element element, Element newElement) {
318 		Element parent = element.getParent();
319 		int i = parent.indexOf(element);
320 		parent.insertChild(newElement, i);
321 	}
322 
323 	/**Inserts an element so that it occurs after a reference element. The new element
324 	 * must not currently have a parent.
325 	 *
326 	 * @param element The reference element.
327 	 * @param neweElement The new element to insert.
328 	 */
insertAfter(Element element, Element neweElement)329 	static void insertAfter(Element element, Element neweElement) {
330 		Element parent = element.getParent();
331 		int i = parent.indexOf(element);
332 		parent.insertChild(neweElement, i + 1);
333 	}
334 
335 	/**
336 	 * Gets the next element. This element need not be a sibling
337 	 * @param element: starting element
338 	 * @return
339 	 */
getNext(Element element)340 	static Element getNext(Element element) {
341 		Element parent = element.getParent();
342 		if (parent == null || parent.getName().equals(XmlDeclarations.MOLECULE_EL)){
343 			return null;
344 		}
345 		int index = parent.indexOf(element);
346 		if (index + 1 >= parent.getChildCount()) {
347 			return getNext(parent);//reached end of element
348 		}
349 		Element next = parent.getChild(index + 1);
350 		while (next.getChildCount() > 0){
351 			next = next.getChild(0);
352 		}
353 		return next;
354 	}
355 
356 	/**
357 	 * Gets the previous element. This element need not be a sibling
358 	 * @param element: starting element
359 	 * @return
360 	 */
getPrevious(Element element)361 	static Element getPrevious(Element element) {
362 		Element parent = element.getParent();
363 		if (parent == null || parent.getName().equals(XmlDeclarations.MOLECULE_EL)){
364 			return null;
365 		}
366 		int index = parent.indexOf(element);
367 		if (index == 0) {
368 			return getPrevious(parent);//reached beginning of element
369 		}
370 		Element previous = parent.getChild(index - 1);
371 		while (previous.getChildCount() > 0){
372 			previous = previous.getChild(previous.getChildCount() - 1);
373 		}
374 		return previous;
375 	}
376 
377 	/**
378 	 * Returns a list containing sibling elements with the given element name after the given element.
379 	 * These elements need not be continuous
380 	 * @param currentElem: the element to look for following siblings of
381 	 * @param elName: the name of the elements desired
382 	 * @return
383 	 */
getNextSiblingsOfType(Element currentElem, String elName)384 	static List<Element> getNextSiblingsOfType(Element currentElem, String elName) {
385 		List<Element> laterSiblingElementsOfType = new ArrayList<Element>();
386 		Element parent = currentElem.getParent();
387 		if (parent == null){
388 			return laterSiblingElementsOfType;
389 		}
390 		int indexOfCurrentElem = parent.indexOf(currentElem);
391 		for (int i = indexOfCurrentElem + 1; i < parent.getChildCount(); i++) {
392 			Element child = parent.getChild(i);
393 			if (child.getName().equals(elName)) {
394 				laterSiblingElementsOfType.add(child);
395 			}
396 		}
397 		return laterSiblingElementsOfType;
398 	}
399 
400 	/**
401 	 * Returns a list containing sibling elements with the given element name after the given element.
402 	 * @param currentElem: the element to look for following siblings of
403 	 * @param elName: the name of the elements desired
404 	 * @return
405 	 */
getNextAdjacentSiblingsOfType(Element currentElem, String elName)406 	static List<Element> getNextAdjacentSiblingsOfType(Element currentElem, String elName) {
407 		List<Element> siblingElementsOfType = new ArrayList<Element>();
408 		Element parent = currentElem.getParent();
409 		if (parent == null){
410 			return siblingElementsOfType;
411 		}
412 		Element nextSibling = getNextSibling(currentElem);
413 		while (nextSibling != null && nextSibling.getName().equals(elName)){
414 			siblingElementsOfType.add(nextSibling);
415 			nextSibling = getNextSibling(nextSibling);
416 		}
417 		return siblingElementsOfType;
418 	}
419 
420 	/**
421 	 * Returns a list containing sibling elements with the given element names after the given element.
422 	 * These elements need not be continuous and are returned in the order encountered
423 	 * @param currentElem: the element to look for following siblings of
424 	 * @param elNames: An array of the names of the elements desired
425 	 * @return
426 	 */
getNextSiblingsOfTypes(Element currentElem, String[] elNames)427 	static List<Element> getNextSiblingsOfTypes(Element currentElem, String[] elNames){
428 		List<Element> laterSiblingElementsOfTypes = new ArrayList<Element>();
429 		currentElem = getNextSibling(currentElem);
430 		while (currentElem != null){
431 			String name = currentElem.getName();
432 			for (String elName : elNames) {
433 				if (name.equals(elName)){
434 					laterSiblingElementsOfTypes.add(currentElem);
435 					break;
436 				}
437 			}
438 			currentElem = getNextSibling(currentElem);
439 		}
440 		return laterSiblingElementsOfTypes;
441 	}
442 
443 	/**
444 	 * Returns a list containing sibling elements with the given element name before the given element.
445 	 * These elements need not be continuous
446 	 * @param currentElem: the element to look for previous siblings of
447 	 * @param elName: the name of the elements desired
448 	 * @return
449 	 */
getPreviousSiblingsOfType(Element currentElem, String elName)450 	static List<Element> getPreviousSiblingsOfType(Element currentElem, String elName) {
451 		List<Element> earlierSiblingElementsOfType = new ArrayList<Element>();
452 		Element parent = currentElem.getParent();
453 		if (parent == null){
454 			return earlierSiblingElementsOfType;
455 		}
456 		int indexOfCurrentElem = parent.indexOf(currentElem);
457 		for (int i = 0; i < indexOfCurrentElem; i++) {
458 			Element child = parent.getChild(i);
459 			if (child.getName().equals(elName)) {
460 				earlierSiblingElementsOfType.add(child);
461 			}
462 		}
463 		return earlierSiblingElementsOfType;
464 	}
465 
466 	/**
467 	 * Gets the next sibling element of the given element. If this element's name is within the elementsToIgnore array this is repeated
468 	 * If no appropriate element can be found null is returned
469 	 * @param startingEl
470 	 * @param elNamesToIgnore
471 	 * @return
472 	 */
getNextSiblingIgnoringCertainElements(Element startingEl, String[] elNamesToIgnore)473 	static Element getNextSiblingIgnoringCertainElements(Element startingEl, String[] elNamesToIgnore){
474 		Element parent = startingEl.getParent();
475 		if (parent == null){
476 			return null;
477 		}
478 		int i = parent.indexOf(startingEl);
479 		if (i + 1 >= parent.getChildCount()) {
480 			return null;
481 		}
482 		Element next = parent.getChild(i + 1);
483 		String elName = next.getName();
484 		for (String namesToIgnore : elNamesToIgnore) {
485 			if (elName.equals(namesToIgnore)){
486 				return getNextSiblingIgnoringCertainElements(next, elNamesToIgnore);
487 			}
488 		}
489 		return next;
490 	}
491 
492 	/**
493 	 * Gets the previous sibling element of the given element. If this element's name is within the elementsToIgnore array this is repeated
494 	 * If no appropriate element can be found null is returned
495 	 * @param startingEl
496 	 * @param elNamesToIgnore
497 	 * @return
498 	 */
getPreviousSiblingIgnoringCertainElements(Element startingEl, String[] elNamesToIgnore)499 	static Element getPreviousSiblingIgnoringCertainElements(Element startingEl, String[] elNamesToIgnore){
500 		Element parent = startingEl.getParent();
501 		if (parent == null){
502 			return null;
503 		}
504 		int i = parent.indexOf(startingEl);
505 		if (i == 0) {
506 			return null;
507 		}
508 		Element previous = parent.getChild(i - 1);
509 		String elName = previous.getName();
510 		for (String namesToIgnore : elNamesToIgnore) {
511 			if (elName.equals(namesToIgnore)){
512 				return getPreviousSiblingIgnoringCertainElements(previous, elNamesToIgnore);
513 			}
514 		}
515 		return previous;
516 	}
517 
518 	/**
519 	 * Finds all descendant elements whose name  matches the given element name
520 	 * @param startingElement
521 	 * @param elementName
522 	 * @return
523 	 */
getDescendantElementsWithTagName(Element startingElement, String elementName)524 	static List<Element> getDescendantElementsWithTagName(Element startingElement, String elementName) {
525 		List<Element> matchingElements = new ArrayList<Element>();
526 		Deque<Element> stack = new ArrayDeque<Element>();
527 		for (int i = startingElement.getChildCount() - 1; i >= 0; i--) {
528 			stack.add(startingElement.getChild(i));
529 		}
530 		while (stack.size() > 0){
531 			Element currentElement = stack.removeLast();
532 			if (currentElement.getName().equals(elementName)){
533 				matchingElements.add(currentElement);
534 			}
535 			for (int i = currentElement.getChildCount() - 1; i >= 0; i--) {
536 				stack.add(currentElement.getChild(i));
537 			}
538 		}
539 		return matchingElements;
540 	}
541 
542 	/**
543 	 * Finds all descendant elements whose element name matches one of the strings in elementNames
544 	 * @param startingElement
545 	 * @param elementNames
546 	 * @return
547 	 */
getDescendantElementsWithTagNames(Element startingElement, String[] elementNames)548 	static List<Element> getDescendantElementsWithTagNames(Element startingElement, String[] elementNames) {
549 		List<Element> matchingElements = new ArrayList<Element>();
550 		Deque<Element> stack = new ArrayDeque<Element>();
551 		for (int i = startingElement.getChildCount() - 1; i >= 0; i--) {
552 			stack.add(startingElement.getChild(i));
553 		}
554 		while (stack.size()>0){
555 			Element currentElement = stack.removeLast();
556 			String currentElName = currentElement.getName();
557 			for (String targetTagName : elementNames) {
558 				if (currentElName.equals(targetTagName)){
559 					matchingElements.add(currentElement);
560 					break;
561 				}
562 			}
563 			for (int i = currentElement.getChildCount() - 1; i >= 0; i--) {
564 				stack.add(currentElement.getChild(i));
565 			}
566 		}
567 		return matchingElements;
568 	}
569 
570 	/**
571 	 * Finds all child elements whose element name matches one of the strings in elementNames
572 	 * @param startingElement
573 	 * @param elementNames
574 	 * @return
575 	 */
getChildElementsWithTagNames(Element startingElement, String[] elementNames)576 	static List<Element> getChildElementsWithTagNames(Element startingElement, String[] elementNames) {
577 		List<Element> matchingElements = new ArrayList<Element>();
578 		for (int i = 0, l = startingElement.getChildCount(); i < l; i++) {
579 			Element child = startingElement.getChild(i);
580 			String currentElName = child.getName();
581 			for (String targetTagName : elementNames) {
582 				if (currentElName.equals(targetTagName)){
583 					matchingElements.add(child);
584 					break;
585 				}
586 			}
587 		}
588 		return matchingElements;
589 	}
590 
591 	/**
592 	 * Finds all descendant elements whose element name matches the given elementName
593 	 * Additionally the element must have the specified attribute and the value of the attribute must be as specified
594 	 * @param startingElement
595 	 * @param elementName
596 	 * @param attributeName
597 	 * @param attributeValue
598 	 * @return
599 	 */
getDescendantElementsWithTagNameAndAttribute(Element startingElement, String elementName, String attributeName, String attributeValue)600 	static List<Element> getDescendantElementsWithTagNameAndAttribute(Element startingElement, String elementName, String attributeName, String attributeValue) {
601 		List<Element> matchingElements = new ArrayList<Element>();
602 		Deque<Element> stack = new ArrayDeque<Element>();
603 		for (int i = startingElement.getChildCount() - 1; i >= 0; i--) {
604 			stack.add(startingElement.getChild(i));
605 		}
606 		while (stack.size() > 0){
607 			Element currentElement =stack.removeLast();
608 			if (currentElement.getName().equals(elementName)){
609 				if (attributeValue.equals(currentElement.getAttributeValue(attributeName))){
610 					matchingElements.add(currentElement);
611 				}
612 			}
613 			for (int i = currentElement.getChildCount() - 1; i >= 0; i--) {
614 				stack.add(currentElement.getChild(i));
615 			}
616 		}
617 		return matchingElements;
618 	}
619 
620 	/**
621 	 * Finds all child elements whose element name matches the given elementName
622 	 * Additionally the element must have the specified attribute and the value of the attribute must be as specified
623 	 * @param startingElement
624 	 * @param elementName
625 	 * @return
626 	 */
getChildElementsWithTagNameAndAttribute(Element startingElement, String elementName, String attributeName, String attributeValue)627 	static List<Element> getChildElementsWithTagNameAndAttribute(Element startingElement, String elementName, String attributeName, String attributeValue) {
628 		List<Element> matchingElements = new ArrayList<Element>();
629 		for (int i = 0, l = startingElement.getChildCount(); i < l; i++) {
630 			Element child = startingElement.getChild(i);
631 			if (child.getName().equals(elementName)){
632 				if (attributeValue.equals(child.getAttributeValue(attributeName))){
633 					matchingElements.add(child);
634 				}
635 			}
636 		}
637 		return matchingElements;
638 	}
639 
640 	/**
641 	 * Finds and returns the number of elements and the number of elements with no children, that are descendants of the startingElement
642 	 * The 0th position of the returned array is the total number of elements
643 	 * The 1st position is the number of child less elements
644 	 * @param startingElement
645 	 * @return
646 	 */
countNumberOfElementsAndNumberOfChildLessElements(Element startingElement)647 	static int[] countNumberOfElementsAndNumberOfChildLessElements(Element startingElement) {
648 		int[] counts = new int[2];
649 		Deque<Element> stack = new ArrayDeque<Element>();
650 		stack.add(startingElement);
651 		while (stack.size() > 0){
652 			Element currentElement = stack.removeLast();
653 			int childCount = currentElement.getChildCount();
654 			if (childCount == 0) {
655 				counts[1]++;
656 			}
657 			else{
658 				stack.addAll(currentElement.getChildElements());
659 				counts[0] += childCount;
660 			}
661 		}
662 		return counts;
663 	}
664 
665 	/**
666 	 * Find all the later siblings of startingElement up until there is no more siblings or an
667 	 * element with the given element name  is reached (exclusive of that element)
668 	 * @param startingEl
669 	 * @param elName
670 	 * @return
671 	 */
getSiblingsUpToElementWithTagName(Element startingEl, String elName)672 	static List<Element> getSiblingsUpToElementWithTagName(Element startingEl, String elName) {
673 		List<Element> laterSiblings = new ArrayList<Element>();
674 		Element nextEl = getNextSibling(startingEl);
675 		while (nextEl != null && !nextEl.getName().equals(elName)){
676 			laterSiblings.add(nextEl);
677 			nextEl = getNextSibling(nextEl);
678 		}
679 		return laterSiblings;
680 	}
681 }
682