GP-155 - Help - fixed intermittent build failure due to different nodes

with shared 'text' attribute values
This commit is contained in:
dragonmacher 2020-11-02 19:25:10 -05:00
parent 798c3abf42
commit 68ef3a22c5
4 changed files with 252 additions and 145 deletions

View file

@ -68,7 +68,6 @@
text="Program Annotations Affecting the Decompiler" text="Program Annotations Affecting the Decompiler"
target="help/topics/DecompilePlugin/DecompilerAnnotations.html"> target="help/topics/DecompilePlugin/DecompilerAnnotations.html">
<tocdef id="AnnoteFunctionBody" sortgroup="a" text="Machine Instructions" target="help/topics/DecompilePlugin/DecompilerAnnotations.html#AnnoteFunctionBody"/> <tocdef id="AnnoteFunctionBody" sortgroup="a" text="Machine Instructions" target="help/topics/DecompilePlugin/DecompilerAnnotations.html#AnnoteFunctionBody"/>
<!-- Space added to text="Comments" attribute below to distinguish it from the CommentsPlugin section of the same name -->
<tocdef id="AnnoteComments" sortgroup="b" text="Comments" target="help/topics/DecompilePlugin/DecompilerAnnotations.html#AnnoteComments"/> <tocdef id="AnnoteComments" sortgroup="b" text="Comments" target="help/topics/DecompilePlugin/DecompilerAnnotations.html#AnnoteComments"/>
<tocdef id="AnnoteVariables" sortgroup="c" text="Variable Annotations" target="help/topics/DecompilePlugin/DecompilerAnnotations.html#AnnoteVariables"/> <tocdef id="AnnoteVariables" sortgroup="c" text="Variable Annotations" target="help/topics/DecompilePlugin/DecompilerAnnotations.html#AnnoteVariables"/>
<tocdef id="AnnotePrototype" sortgroup="d" text="Function Prototypes" target="help/topics/DecompilePlugin/DecompilerAnnotations.html#AnnotePrototype"/> <tocdef id="AnnotePrototype" sortgroup="d" text="Function Prototypes" target="help/topics/DecompilePlugin/DecompilerAnnotations.html#AnnotePrototype"/>

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,14 +15,14 @@
*/ */
package help; package help;
import help.validator.LinkDatabase;
import help.validator.model.*;
import java.io.*; import java.io.*;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.*; import java.util.*;
import help.validator.LinkDatabase;
import help.validator.model.*;
/** /**
* A class that will take in a group of help directories and create a tree of * A class that will take in a group of help directories and create a tree of
* help Table of Contents (TOC) items. Ideally, this tree can be used to create a single * help Table of Contents (TOC) items. Ideally, this tree can be used to create a single
@ -33,7 +32,6 @@ import java.util.*;
* We call this class an <b>overlay</b> tree to drive home the idea that each * We call this class an <b>overlay</b> tree to drive home the idea that each
* help directory's TOC data is put into the tree, with any duplicate paths overlayed * help directory's TOC data is put into the tree, with any duplicate paths overlayed
* on top of those from other help directories. * on top of those from other help directories.
*
*/ */
public class OverlayHelpTree { public class OverlayHelpTree {
@ -44,10 +42,13 @@ public class OverlayHelpTree {
public OverlayHelpTree(TOCItemProvider tocItemProvider, LinkDatabase linkDatabase) { public OverlayHelpTree(TOCItemProvider tocItemProvider, LinkDatabase linkDatabase) {
this.linkDatabase = linkDatabase; this.linkDatabase = linkDatabase;
for (TOCItemExternal external : tocItemProvider.getTOCItemExternalsByDisplayMapping().values()) { for (TOCItemExternal external : tocItemProvider.getTOCItemExternalsByDisplayMapping()
.values()) {
addExternalTOCItem(external); addExternalTOCItem(external);
} }
for (TOCItemDefinition definition : tocItemProvider.getTOCItemDefinitionsByIDMapping().values()) {
for (TOCItemDefinition definition : tocItemProvider.getTOCItemDefinitionsByIDMapping()
.values()) {
addSourceTOCItem(definition); addSourceTOCItem(definition);
} }
} }
@ -167,12 +168,6 @@ public class OverlayHelpTree {
return false; return false;
} }
// TODO delete this debug
// Set<Entry<String, Set<TOCItem>>> entrySet = parentToChildrenMap.entrySet();
// for (Entry<String, Set<TOCItem>> entry : entrySet) {
// System.out.println(entry.getKey() + " -> " + entry.getValue());
// }
OverlayNode newRootNode = new OverlayNode(null, rootItem); OverlayNode newRootNode = new OverlayNode(null, rootItem);
buildChildren(newRootNode); buildChildren(newRootNode);
@ -255,6 +250,7 @@ public class OverlayHelpTree {
} }
} }
// TODO LOOKIE
private static final Comparator<OverlayNode> CHILD_SORT_COMPARATOR = private static final Comparator<OverlayNode> CHILD_SORT_COMPARATOR =
new Comparator<OverlayNode>() { new Comparator<OverlayNode>() {
@Override @Override
@ -283,7 +279,16 @@ public class OverlayHelpTree {
return -1; return -1;
} }
return text1.compareTo(text2); int result = text1.compareTo(text2);
if (result != 0) {
return result;
}
// At this point we have 2 nodes that have the same text attribute as children of
// a <TOCDEF> tag. This is OK, as we use text only for sorting, but not for the
// display text. Use the ID as a tie-breaker for sorting, which should provide
// sorting consistency.
return o1.getIDAttribute().compareTo(o2.getIDAttribute()); // ID should not be null
} }
}; };
} }

View file

@ -15,12 +15,12 @@
*/ */
package help.validator.model; package help.validator.model;
import help.validator.LinkDatabase;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.*; import java.util.*;
import help.validator.LinkDatabase;
/** /**
* A Table of Contents entry, which is represented in the help output as an xml tag. * A Table of Contents entry, which is represented in the help output as an xml tag.
*/ */
@ -63,7 +63,8 @@ public abstract class TOCItem {
String sortPreference, int lineNumber) { String sortPreference, int lineNumber) {
this.parentItem = parentItem; this.parentItem = parentItem;
this.sourceFile = sourceFile; this.sourceFile = sourceFile;
this.IDAttribute = ID; this.IDAttribute = Objects.requireNonNull(ID,
"TOC Tag missing 'id' attribute: " + sourceFile + ":" + lineNumber);
this.textAttribute = text; this.textAttribute = text;
this.targetAttribute = target; this.targetAttribute = target;
@ -157,82 +158,108 @@ public abstract class TOCItem {
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (this == obj) if (this == obj) {
return true; return true;
if (obj == null) }
if (obj == null) {
return false; return false;
if (getClass() != obj.getClass()) }
if (getClass() != obj.getClass()) {
return false; return false;
}
TOCItem other = (TOCItem) obj; TOCItem other = (TOCItem) obj;
if (IDAttribute == null) { if (IDAttribute == null) {
if (other.IDAttribute != null) if (other.IDAttribute != null) {
return false; return false;
} }
else if (!IDAttribute.equals(other.IDAttribute)) }
else if (!IDAttribute.equals(other.IDAttribute)) {
return false; return false;
}
if (sortPreference == null) { if (sortPreference == null) {
if (other.sortPreference != null) if (other.sortPreference != null) {
return false; return false;
} }
else if (!sortPreference.equals(other.sortPreference)) }
else if (!sortPreference.equals(other.sortPreference)) {
return false; return false;
}
if (sourceFile == null) { if (sourceFile == null) {
if (other.sourceFile != null) if (other.sourceFile != null) {
return false; return false;
} }
else if (!sourceFile.equals(other.sourceFile)) }
else if (!sourceFile.equals(other.sourceFile)) {
return false; return false;
}
if (targetAttribute == null) { if (targetAttribute == null) {
if (other.targetAttribute != null) if (other.targetAttribute != null) {
return false; return false;
} }
else if (!targetAttribute.equals(other.targetAttribute)) }
else if (!targetAttribute.equals(other.targetAttribute)) {
return false; return false;
}
if (textAttribute == null) { if (textAttribute == null) {
if (other.textAttribute != null) if (other.textAttribute != null) {
return false; return false;
} }
else if (!textAttribute.equals(other.textAttribute)) }
else if (!textAttribute.equals(other.textAttribute)) {
return false; return false;
}
return true; return true;
} }
/** /**
* True if the two items are the same, except that they come from a different source file. * True if the two items are the same, except that they come from a different source file.
* @param other the other item
* @return true if equivalent
*/ */
public boolean isEquivalent(TOCItem other) { public boolean isEquivalent(TOCItem other) {
if (this == other) if (this == other) {
return true; return true;
if (other == null) }
if (other == null) {
return false; return false;
if (getClass() != other.getClass()) }
if (getClass() != other.getClass()) {
return false; return false;
}
if (IDAttribute == null) { if (IDAttribute == null) {
if (other.IDAttribute != null) if (other.IDAttribute != null) {
return false; return false;
} }
else if (!IDAttribute.equals(other.IDAttribute)) }
else if (!IDAttribute.equals(other.IDAttribute)) {
return false; return false;
}
if (sortPreference == null) { if (sortPreference == null) {
if (other.sortPreference != null) if (other.sortPreference != null) {
return false; return false;
} }
else if (!sortPreference.equals(other.sortPreference)) }
else if (!sortPreference.equals(other.sortPreference)) {
return false; return false;
}
if (targetAttribute == null) { if (targetAttribute == null) {
if (other.targetAttribute != null) if (other.targetAttribute != null) {
return false; return false;
} }
else if (!targetAttribute.equals(other.targetAttribute)) }
else if (!targetAttribute.equals(other.targetAttribute)) {
return false; return false;
}
if (textAttribute == null) { if (textAttribute == null) {
if (other.textAttribute != null) if (other.textAttribute != null) {
return false; return false;
} }
else if (!textAttribute.equals(other.textAttribute)) }
else if (!textAttribute.equals(other.textAttribute)) {
return false; return false;
}
return true; return true;
} }
@ -253,7 +280,8 @@ public abstract class TOCItem {
} }
} }
public String generateTOCItemTag(LinkDatabase linkDatabase, boolean isInlineTag, int indentLevel) { public String generateTOCItemTag(LinkDatabase linkDatabase, boolean isInlineTag,
int indentLevel) {
StringBuilder buildy = new StringBuilder(); StringBuilder buildy = new StringBuilder();
buildy.append(INDENTS[indentLevel]); buildy.append(INDENTS[indentLevel]);
buildy.append('<').append(TOC_TAG_NAME).append(' '); buildy.append('<').append(TOC_TAG_NAME).append(' ');

View file

@ -15,7 +15,7 @@
*/ */
package help; package help;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.*;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
@ -68,12 +68,12 @@ public class OverlayHelpTreeTest {
Path tocSourceFile = Paths.get("/fake/path_2/TOC_Source.xml"); Path tocSourceFile = Paths.get("/fake/path_2/TOC_Source.xml");
String root_ID = root.getIDAttribute(); String root_ID = root.getIDAttribute();
TOCItemReference root_ref = referenceItem(root_ID, tocSourceFile); TOCItemReference root_ref = tocref(root_ID, tocSourceFile);
String child_1_ID = child_1.getIDAttribute(); String child_1_ID = child_1.getIDAttribute();
TOCItemReference child_1_ref = referenceItem(root_ref, child_1_ID, tocSourceFile); TOCItemReference child_1_ref = tocref(root_ref, child_1_ID, tocSourceFile);
TOCItemDefinition child_2 = definitionItem(child_1_ref, "child_2", tocSourceFile); TOCItemDefinition child_2 = tocdef(child_1_ref, "child_2", tocSourceFile);
TOCItemProviderTestStub tocProvider = new TOCItemProviderTestStub(); TOCItemProviderTestStub tocProvider = new TOCItemProviderTestStub();
tocProvider.addExternal(root); tocProvider.addExternal(root);
@ -88,6 +88,57 @@ public class OverlayHelpTreeTest {
assertOrder(spy, 3, child_2); assertOrder(spy, 3, child_2);
} }
@Test
public void testSourceTOCFileThatDependsAnotherTOCSourceFile() {
/*
The first source file defines attributes that the second file references.
Example makeup we will create:
TOC_Source.xml
<tocdef id="root" target="fake">
<tocdef id="child_1" target="fake" />
</tocdef>
Another TOC_Source.xml
<tocref id="root">
<tocref="child_1">
<tocdef id="child_2" target="fake" />
</tocref>
</tocref>
*/
Path toc_1 = Paths.get("/fake/path_1/TOC_Source.xml");
TOCItemDefinition root = tocdef("root", toc_1);
TOCItemDefinition child_1 = tocdef(root, "child_1", toc_1);
Path toc_2 = Paths.get("/fake/path_2/TOC_Source.xml");
String root_ID = root.getIDAttribute();
String child_1_ID = child_1.getIDAttribute();
TOCItemReference root_ref = tocref(root_ID, toc_2);
TOCItemReference child_1_ref = tocref(root_ref, child_1_ID, toc_2);
TOCItemDefinition child_2 = tocdef(child_1_ref, "child_2", toc_2);
TOCItemProviderTestStub tocProvider = new TOCItemProviderTestStub();
tocProvider.addDefinition(root);
tocProvider.addDefinition(child_1);
tocProvider.addDefinition(child_2);// in the second TOC file
TOCSpyWriter spy = printOverlayTree(tocProvider, toc_2);
assertNodeCount(spy, 3);
assertOrder(spy, 1, root);
assertOrder(spy, 2, child_1);
assertOrder(spy, 3, child_2);
}
@Test @Test
public void testSourceTOCFileThatDependsUponPreBuiltHelp_MultiplePreBuiltInputs() { public void testSourceTOCFileThatDependsUponPreBuiltHelp_MultiplePreBuiltInputs() {
// //
@ -131,18 +182,17 @@ public class OverlayHelpTreeTest {
TOCItemExternal prebuilt_a_child = externalItem(child_1_a, "prebuilt_a_child"); TOCItemExternal prebuilt_a_child = externalItem(child_1_a, "prebuilt_a_child");
// note: same ID values, since they represent the same nodes, but from different TOC files // note: same ID values, since they represent the same nodes, but from different TOC files
TOCItemExternal root_b = externalItemAlt(null, "root"); TOCItemExternal root_b = externalItem(null, "root");
TOCItemExternal child_1_b = externalItemAlt(root_b, "child_1"); TOCItemExternal child_1_b = externalItem(root_b, "child_1");
TOCItemExternal prebuilt_b_child = externalItemAlt(child_1_b, "prebuilt_b_child"); TOCItemExternal prebuilt_b_child = externalItem(child_1_b, "prebuilt_b_child");
Path tocSourceFile = Paths.get("/fake/path_2/TOC_Source.xml"); Path tocSourceFile = Paths.get("/fake/path_2/TOC_Source.xml");
String root_ID = root_a.getIDAttribute(); String root_ID = root_a.getIDAttribute();
TOCItemReference root_ref = referenceItem(root_ID, tocSourceFile); TOCItemReference root_ref = tocref(root_ID, tocSourceFile);
String child_1_ID = child_1_a.getIDAttribute(); String child_1_ID = child_1_a.getIDAttribute();
TOCItemReference child_1_ref = referenceItem(root_ref, child_1_ID, tocSourceFile); TOCItemReference child_1_ref = tocref(root_ref, child_1_ID, tocSourceFile);
TOCItemDefinition child_2 = tocdef(child_1_ref, "child_2", tocSourceFile);
TOCItemDefinition child_2 = definitionItem(child_1_ref, "child_2", tocSourceFile);
TOCItemProviderTestStub tocProvider = new TOCItemProviderTestStub(); TOCItemProviderTestStub tocProvider = new TOCItemProviderTestStub();
tocProvider.addExternal(root_a); tocProvider.addExternal(root_a);
@ -166,54 +216,79 @@ public class OverlayHelpTreeTest {
} }
@Test @Test
public void testSourceTOCFileThatDependsAnotherTOCSourceFile() { public void testSourceTOCFileThatHasNodeWithSameTextAttributeAsOneOfItsExternalModluleDependencies() {
/* /*
The first source file defines attributes that the second file references. The first source file defines attributes that the second file references. Both files
will have multiple nodes that coincidentally share 'text' attribute values.
Note: the 'id' attributes have to be unique; the 'text' attributes do not have to be unique
Example makeup we will create: Example makeup we will create:
TOC_Source.xml PreBuild_TOC.xml
<tocdef id="root" target="fake"> <tocitem id="root" target="fake">
<tocdef id="child_1" target="fake" /> <tocitem id="child_1_1" text="Child 1" target="fake" />
</tocdef> </tocitem>
Another PreBuild_TOC.xml
<tocitem id="root" target="fake">
<tocitem id="child_2_1" text=Child 1" target="fake" />
<tocitem id="child_2_2" text=Child 2" target="fake" />
</tocitem>
Another TOC_Source.xml Another TOC_Source.xml
<tocref id="root"> <tocref id="root">
<tocref="child_1"> <tocref="child_1_1">
<tocdef id="child_2" target="fake" /> <tocdef id="child_2_1a" text="Child 1a" target="fake" />
</tocref> </tocref>
<tocdef id="child_3_2" text="Child 2" target="fake" />
</tocref> </tocref>
*/ */
Path toc_1 = Paths.get("/fake/path_1/TOC_Source.xml"); TOCItemExternal root_a = externalItem("root");
TOCItemDefinition root = definitionItem("root", toc_1); TOCItemExternal child_1_1 = externalItem(root_a, "child_1_1", "Child 1");
TOCItemDefinition child_1 = definitionItem(root, "child_1", toc_1);
Path toc_2 = Paths.get("/fake/path_2/TOC_Source.xml"); // note: same ID values, since they represent the same nodes, but from different TOC files
String root_ID = root.getIDAttribute(); TOCItemExternal root_b = externalItem(null, "root");
String child_1_ID = child_1.getIDAttribute(); TOCItemExternal child_2_1 = externalItem(root_b, "child_2_1", "Child 1");
TOCItemExternal child_2_2 = externalItem(root_b, "child_2_2", "Child 2");
TOCItemReference root_ref = referenceItem(root_ID, toc_2); Path toc = Paths.get("/fake/path_2/TOC_Source.xml");
TOCItemReference child_1_ref = referenceItem(root_ref, child_1_ID, toc_2); String root_ID = root_a.getIDAttribute();
TOCItemDefinition child_2 = definitionItem(child_1_ref, "child_2", toc_2); String child_1_ID = child_1_1.getIDAttribute();
TOCItemReference root_ref = tocref(root_ID, toc);
TOCItemReference child_1_ref = tocref(root_ref, child_1_ID, toc);
TOCItemDefinition child_2_1a = tocdef(child_1_ref, "child_2_1a", "Child 1a", toc);
TOCItemDefinition child_3_2 = tocdef(root_ref, "child_3_2", "Child 2", toc);
TOCItemProviderTestStub tocProvider = new TOCItemProviderTestStub(); TOCItemProviderTestStub tocProvider = new TOCItemProviderTestStub();
tocProvider.addDefinition(root); tocProvider.addExternal(root_a);
tocProvider.addDefinition(child_1); tocProvider.addExternal(child_1_1);
tocProvider.addDefinition(child_2);// in the second TOC file tocProvider.addExternal(root_b);
tocProvider.addExternal(child_2_1);
tocProvider.addExternal(child_2_2);
tocProvider.addDefinition(child_2_1a); // in the first external TOC file
tocProvider.addDefinition(child_3_2);
TOCSpyWriter spy = printOverlayTree(tocProvider, toc_2); TOCSpyWriter spy = printOverlayTree(tocProvider, toc);
assertNodeCount(spy, 3); assertNodeCount(spy, 4);
assertOrder(spy, 1, root); assertOrder(spy, 1, root_a);
assertOrder(spy, 2, child_1); assertOrder(spy, 2, child_1_1);
assertOrder(spy, 3, child_2); assertOrder(spy, 3, child_2_1a);
assertOrder(spy, 4, child_3_2);
// note: prebuilt_a_child and prebuilt_b_child don't get output, since they do not have
// the same TOC file ID as the help file being processed (in other words, they don't
// live in the TOC_Source.xml being processes, so they are not part of the output).
} }
//================================================================================================== //==================================================================================================
@ -242,22 +317,26 @@ public class OverlayHelpTreeTest {
return spy; return spy;
} }
private TOCItemDefinition definitionItem(String ID, Path tocSourceFile) { private TOCItemDefinition tocdef(String ID, Path tocSourceFile) {
return definitionItem(null, ID, tocSourceFile); return tocdef(null, ID, tocSourceFile);
} }
private TOCItemDefinition definitionItem(TOCItem parent, String ID, Path tocSourceFile) { private TOCItemDefinition tocdef(TOCItem parent, String ID, Path tocSourceFile) {
return tocdef(parent, ID, ID, tocSourceFile);
}
private TOCItemDefinition tocdef(TOCItem parent, String ID, String text, Path tocSourceFile) {
String target = "fake"; String target = "fake";
String sort = ""; String sort = "";
int line = 1; int line = 1;
return new TOCItemDefinition(parent, tocSourceFile, ID, ID, target, sort, line); return new TOCItemDefinition(parent, tocSourceFile, ID, text, target, sort, line);
} }
private TOCItemReference referenceItem(String referenceID, Path tocSourceFile) { private TOCItemReference tocref(String referenceID, Path tocSourceFile) {
return referenceItem(null, referenceID, tocSourceFile); return tocref(null, referenceID, tocSourceFile);
} }
private TOCItemReference referenceItem(TOCItem parent, String referenceID, Path tocSourceFile) { private TOCItemReference tocref(TOCItem parent, String referenceID, Path tocSourceFile) {
return new TOCItemReference(parent, tocSourceFile, referenceID, 1); return new TOCItemReference(parent, tocSourceFile, referenceID, 1);
} }
@ -266,24 +345,20 @@ public class OverlayHelpTreeTest {
} }
private TOCItemExternal externalItem(TOCItem parent, String ID) { private TOCItemExternal externalItem(TOCItem parent, String ID) {
Path tocFile = Paths.get("/fake/path_1/PreBuild_TOC.xml"); return externalItem(parent, ID, ID);
String target = "fake";
String sort = "";
int line = 1;
return new TOCItemExternal(parent, tocFile, ID, ID, target, sort, line);
} }
private TOCItemExternal externalItemAlt(TOCItem parent, String ID) { private TOCItemExternal externalItem(TOCItem parent, String ID, String text) {
Path tocFile = Paths.get("/fake/path_1/PreBuild_TOC.xml"); Path tocFile = Paths.get("/fake/path_1/PreBuild_TOC.xml");
String target = "fake"; String target = "fake";
String sort = ""; String sort = "";
int line = 1; int line = 1;
return new TOCItemExternal(parent, tocFile, ID, ID, target, sort, line); return new TOCItemExternal(parent, tocFile, ID, text, target, sort, line);
} }
private void assertOrder(TOCSpyWriter spy, int ordinal, TOCItem item) { private void assertOrder(TOCSpyWriter spy, int ordinal, TOCItem item) {
String ID = spy.getItem(ordinal - 1 /* make an index */); String ID = spy.getItem(ordinal - 1 /* make an index */);
assertEquals("Did not find TOC item at expected index: " + ordinal, item.getIDAttribute(), assertEquals("Did not find TOC item at expected ordinal: " + ordinal, item.getIDAttribute(),
ID); ID);
} }
@ -324,7 +399,7 @@ public class OverlayHelpTreeTest {
private void storeDisplayAttribute(String s) { private void storeDisplayAttribute(String s) {
// create a pattern to pull out the display string // create a pattern to pull out the display string
Pattern p = Pattern.compile(".*display=\"(.*)\" toc_id.*"); Pattern p = Pattern.compile(".*toc_id=\"(.*)\".*");
Matcher matcher = p.matcher(s.trim()); Matcher matcher = p.matcher(s.trim());
if (!matcher.matches()) { if (!matcher.matches()) {
@ -347,8 +422,8 @@ public class OverlayHelpTreeTest {
Map<String, TOCItemDefinition> definitions = new HashMap<>(); Map<String, TOCItemDefinition> definitions = new HashMap<>();
void addExternal(TOCItemExternal item) { void addExternal(TOCItemExternal item) {
String displayText = item.getIDAttribute(); String ID = item.getIDAttribute();
externals.put(displayText, item); externals.put(ID, item);
} }
void addDefinition(TOCItemDefinition item) { void addDefinition(TOCItemDefinition item) {