Skip to content

Commit e7db996

Browse files
committed
fix outline generation
1 parent 42f2da7 commit e7db996

File tree

3 files changed

+239
-7
lines changed

3 files changed

+239
-7
lines changed

api-docs/cppdocs/Doxyfile-HTML

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -912,9 +912,7 @@ EXCLUDE_SYMBOLS = BinaryNinja::LowLevelILInstructionAccessor \
912912
BinaryNinja::MediumLevelILInstructionAccessor \
913913
BinaryNinja::MediumLevelILInstructionAccessor* \
914914
BinaryNinja::HighLevelILInstructionAccessor \
915-
BinaryNinja::HighLevelILInstructionAccessor* \
916-
metadata \
917-
platform
915+
BinaryNinja::HighLevelILInstructionAccessor*
918916

919917

920918
#EXCLUDE_SYMBOLS = QProgressIndicator #Broke Exhale

api-docs/cppdocs/binaryninja-docs.css

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,10 @@ tr#projectrow {
587587
#nav-tree .arrow {
588588
opacity: 0.6;
589589
transition: opacity 0.2s;
590+
display: inline-block;
591+
min-width: 32px;
592+
text-align: center;
593+
box-sizing: border-box;
590594
}
591595

592596
#nav-tree .item:hover .arrow {
@@ -633,8 +637,31 @@ a.index\.html {
633637
display: none;
634638
}
635639

636-
#nav-tree-contents > ul > li > .item {
637-
height: 0px;
640+
/* Doxygen 1.15 creates a wrapper node - hide it but show its children */
641+
#nav-tree-contents > ul > li:first-child > .item {
642+
display: none !important;
643+
}
644+
645+
/* Make the wrapper's children appear at the top level by removing their indentation */
646+
#nav-tree-contents > ul > li:first-child > ul {
647+
padding-left: 0 !important;
648+
margin-left: 0 !important;
649+
}
650+
651+
/* No compensation needed - arrows are now consistent */
652+
#nav-tree-contents > ul > li:first-child > ul > li > .item {
653+
margin-left: 0;
654+
}
655+
656+
/* Add spacing between top-level items */
657+
#nav-tree-contents > ul > li:first-child > ul > li {
658+
margin-top: 6px;
659+
padding-top: 2px;
660+
padding-bottom: 2px;
661+
}
662+
663+
#nav-tree-contents > ul > li:first-child > ul > li:first-child {
664+
margin-top: 0;
638665
}
639666

640667
/* ============================================================================

api-docs/cppdocs/build_min_docs.py

Lines changed: 209 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,176 @@ def load_items_in_file(filename):
8080
return items
8181

8282

83+
def convert_navtree_format(js_content):
84+
"""
85+
Fix Doxygen 1.15+ NAVTREE format.
86+
Doxygen 1.15 incorrectly wraps all top-level items as children of the first item.
87+
Instead of unwrapping (which breaks breadcrumbs), we'll just reorder items and
88+
use CSS to hide the wrapper node.
89+
90+
Incorrect (Doxygen 1.15):
91+
var NAVTREE = [ ["Root", "index.html", [["Item1",...], ["Deprecated",...], ["Item2",...]] ] ];
92+
93+
We'll reorder to:
94+
var NAVTREE = [ ["Root", "index.html", [["Item1",...], ["Item2",...], ["Deprecated",...]] ] ];
95+
"""
96+
# Find NAVTREE definition
97+
navtree_pattern = r'var NAVTREE\s*=\s*\[(.*?)\];'
98+
match = re.search(navtree_pattern, js_content, re.DOTALL)
99+
100+
if not match:
101+
print("WARNING: NAVTREE not found, skipping format conversion")
102+
return js_content
103+
104+
navtree_content = match.group(1).strip()
105+
106+
print("Checking NAVTREE format...")
107+
108+
# Parse the NAVTREE array
109+
try:
110+
# Wrap in array brackets to make it valid JSON
111+
navtree_array = json.loads('[' + navtree_content + ']')
112+
except json.JSONDecodeError as e:
113+
print(f"WARNING: Could not parse NAVTREE as JSON: {e}")
114+
return js_content
115+
116+
# Check if this is the Doxygen 1.15 format:
117+
# - Single top-level item
118+
# - That item has an array of children as its 3rd element
119+
if len(navtree_array) == 1 and isinstance(navtree_array[0], list) and len(navtree_array[0]) == 3:
120+
root_item = navtree_array[0]
121+
children = root_item[2]
122+
123+
if isinstance(children, list) and len(children) > 0:
124+
print(f"Detected Doxygen 1.15 format with wrapper containing {len(children)} items")
125+
126+
# Doxygen 1.15 reorders items incorrectly
127+
# Correct order should be: Binary Ninja C++ API, Topics, Namespaces, Classes, Deprecated List
128+
# Doxygen 1.15 puts: Binary Ninja C++ API, Deprecated List, Topics, Namespaces, Classes
129+
# Let's reorder to put Deprecated List at the end
130+
deprecated_idx = None
131+
for i, item in enumerate(children):
132+
if item[0] == "Deprecated List":
133+
deprecated_idx = i
134+
break
135+
136+
if deprecated_idx is not None and deprecated_idx > 0:
137+
# Move Deprecated List to the end
138+
deprecated_item = children.pop(deprecated_idx)
139+
children.append(deprecated_item)
140+
print(f"Reordered: moved 'Deprecated List' from position {deprecated_idx} to end")
141+
142+
# Keep the wrapper structure, just with reordered children
143+
root_item[2] = children
144+
fixed_array = [root_item]
145+
146+
# Convert back to JavaScript
147+
fixed_json = json.dumps(fixed_array, indent=None, separators=(',', ':'))
148+
149+
# Replace in original content
150+
new_navtree_def = f'var NAVTREE = {fixed_json};'
151+
result = js_content[:match.start()] + new_navtree_def + js_content[match.end():]
152+
153+
# Adjust NAVTREEINDEX breadcrumbs to account for the reordering
154+
result = adjust_breadcrumbs_for_reorder(result, deprecated_idx)
155+
156+
print(f"Fixed NAVTREE: kept wrapper structure, reordered children")
157+
return result
158+
159+
print("NAVTREE format appears correct, no conversion needed")
160+
return js_content
161+
162+
163+
def adjust_breadcrumbs_for_reorder(js_content, deprecated_moved_from_idx):
164+
"""
165+
Adjust breadcrumbs in NAVTREEINDEX to account for moving Deprecated List.
166+
167+
Original order (Doxygen 1.15): [0: Binary Ninja, 1: Deprecated, 2: Topics, 3: Namespaces, 4: Classes]
168+
New order after reorder: [0: Binary Ninja, 1: Topics, 2: Namespaces, 3: Classes, 4: Deprecated]
169+
170+
So breadcrumbs need adjustment:
171+
- Items at old index 1 (Deprecated) -> new index 4
172+
- Items at old index 2+ -> subtract 1
173+
"""
174+
if deprecated_moved_from_idx is None:
175+
return js_content
176+
177+
print(f"Adjusting NAVTREEINDEX breadcrumbs for reordering (Deprecated moved from {deprecated_moved_from_idx} to end)...")
178+
179+
result = []
180+
pos = 0
181+
adjusted_count = 0
182+
183+
while True:
184+
# Find next NAVTREEINDEX variable
185+
match = re.search(r'var (NAVTREEINDEX\d*)\s*=\s*\{', js_content[pos:])
186+
if not match:
187+
result.append(js_content[pos:])
188+
break
189+
190+
# Add everything before this match
191+
result.append(js_content[pos:pos + match.start()])
192+
193+
var_name = match.group(1)
194+
obj_start = pos + match.end() - 1 # Position of the opening {
195+
196+
# Find the matching closing } by counting braces
197+
brace_count = 1
198+
i = obj_start + 1
199+
while i < len(js_content) and brace_count > 0:
200+
if js_content[i] == '{':
201+
brace_count += 1
202+
elif js_content[i] == '}':
203+
brace_count -= 1
204+
i += 1
205+
206+
if brace_count != 0:
207+
print(f"WARNING: Could not find closing brace for {var_name}")
208+
result.append(js_content[pos:])
209+
break
210+
211+
obj_end = i # Position after the closing }
212+
obj_content = js_content[obj_start:obj_end]
213+
214+
try:
215+
# Parse the index object
216+
index_obj = json.loads(obj_content)
217+
adjusted_obj = {}
218+
219+
for key, breadcrumbs in index_obj.items():
220+
if isinstance(breadcrumbs, list) and len(breadcrumbs) > 0:
221+
# Adjust the first breadcrumb index for the reordering
222+
first_idx = breadcrumbs[0]
223+
new_breadcrumbs = breadcrumbs.copy()
224+
225+
if first_idx == deprecated_moved_from_idx:
226+
# This points to Deprecated List, now at the end
227+
new_breadcrumbs[0] = 4
228+
elif first_idx > deprecated_moved_from_idx:
229+
# This was after Deprecated, shift down by 1
230+
new_breadcrumbs[0] = first_idx - 1
231+
232+
adjusted_obj[key] = new_breadcrumbs
233+
else:
234+
adjusted_obj[key] = breadcrumbs
235+
236+
# Convert back to JavaScript
237+
adjusted_json = json.dumps(adjusted_obj, indent=None, separators=(',', ':'))
238+
result.append(f'var {var_name} = {adjusted_json};')
239+
adjusted_count += 1
240+
241+
except json.JSONDecodeError as e:
242+
print(f"WARNING: Could not parse {var_name}: {e}")
243+
result.append(js_content[pos:obj_end])
244+
245+
pos = obj_end
246+
247+
print(f"Adjusted {adjusted_count} NAVTREEINDEX variables for reordering")
248+
return ''.join(result)
249+
250+
251+
252+
83253
def replace_getScript_function(js_content, replacement_func):
84254
"""
85255
Robustly find and replace the getScript function definition.
@@ -134,6 +304,12 @@ def minifier():
134304
for mod in load_items_in_file("html/annotated.js"):
135305
navtree_built_data += mod + "\n"
136306

307+
# Load navtreedata.js which contains NAVTREE and NAVTREEINDEX definitions (Doxygen 1.15+)
308+
if os.path.exists("html/navtreedata.js"):
309+
with open("html/navtreedata.js", "r") as fp:
310+
navtree_built_data += fp.read() + "\n"
311+
deletion_queue.append("html/navtreedata.js")
312+
137313
# The navtree indices also need to be loaded in since we're modifying how navbar.js::getScript works.
138314
# This also saves another ~60 files.
139315
for nav_tree_index_file in os.listdir("html"):
@@ -145,18 +321,23 @@ def minifier():
145321
while "\n\n" in navtree_built_data:
146322
navtree_built_data = navtree_built_data.replace("\n\n", "\n")
147323

324+
# Fix Doxygen 1.15 NAVTREE format BEFORE removing newlines
325+
navtree_built_data = convert_navtree_format(navtree_built_data)
326+
148327
navtree_built_data = navtree_built_data.replace("\n", "")
149328

150329
fp = open("html/navtree.js", "r")
151330
navtree_orig = fp.read()
152331
fp.close()
153332

154333
# getScript(scriptName,func,show) here originally loads the js file and calls func once that is complete
155-
# Here, we just want to skip the whole process and immediately call the callback.
334+
# Here, we just want to skip the whole process and call the callback.
335+
# We use setTimeout(0) to make it async, which may be important for the tree sync logic
156336
# This replacement works across different Doxygen versions (tested with 1.12.0, 1.14.0, and 1.15.0)
157-
nav_tree_fixed_get_script = "function getScript(scriptName,func,show) { func(); }"
337+
nav_tree_fixed_get_script = "function getScript(scriptName,func,show) { setTimeout(func, 0); }"
158338

159339
nav_tree_fixed = replace_getScript_function(navtree_orig, nav_tree_fixed_get_script)
340+
160341
navtree = navtree_built_data + "\n" + nav_tree_fixed
161342

162343
fp = open("html/navtree.js", "w")
@@ -200,6 +381,31 @@ def build_doxygen(args):
200381
print(f"Created docset with status code {stat}")
201382

202383

384+
def remove_navtreedata_references():
385+
"""
386+
Remove references to navtreedata.js from HTML files since we've inlined it into navtree.js
387+
"""
388+
import glob
389+
html_files = glob.glob("html/**/*.html", recursive=True)
390+
count = 0
391+
for html_file in html_files:
392+
with open(html_file, 'r') as f:
393+
content = f.read()
394+
395+
# Remove the navtreedata.js script tag
396+
if 'navtreedata.js' in content:
397+
new_content = re.sub(
398+
r'<script type="text/javascript" src="navtreedata\.js"></script>\s*\n',
399+
'',
400+
content
401+
)
402+
with open(html_file, 'w') as f:
403+
f.write(new_content)
404+
count += 1
405+
406+
print(f'Removed navtreedata.js references from {count} HTML files')
407+
408+
203409
def main():
204410
parser = argparse.ArgumentParser(prog=sys.argv[0])
205411
parser.add_argument("--docset", action="store_true", default=False, help="Generate Dash docset")
@@ -209,6 +415,7 @@ def main():
209415
print("Minifying Output")
210416
if os.path.exists("html/navtree.js"):
211417
minifier()
418+
remove_navtreedata_references()
212419
for file in deletion_queue:
213420
file = "./" + file
214421
os.remove(file)

0 commit comments

Comments
 (0)