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