1 /* 2 * Copyright (C) 2010 Google Inc. 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 17 package com.google.doclava; 18 19 import java.util.Arrays; 20 import java.util.ArrayList; 21 import java.util.Collections; 22 import java.util.Comparator; 23 import java.util.List; 24 import java.util.regex.Pattern; 25 import java.util.regex.Matcher; 26 import java.io.File; 27 28 import com.google.clearsilver.jsilver.data.Data; 29 30 /** 31 * Represents a browsable sample code project, with methods for managing 32 * metadata collection, file output, sorting, etc. 33 */ 34 public class SampleCode { 35 String mSource; 36 String mDest; 37 String mTitle; 38 String mProjectDir; 39 String mTags; 40 41 /** Max size for browseable images/video. If a source file exceeds this size, 42 * a file is generated with a generic placeholder and the original file is not 43 * copied to out. 44 */ 45 private static final double MAX_FILE_SIZE_BYTES = 2097152; 46 47 /** When full tree nav is enabled, generate an index for every dir 48 * and linkify the breadcrumb paths in all files. 49 */ 50 private static final boolean FULL_TREE_NAVIGATION = false; 51 52 public SampleCode(String source, String dest, String title) { 53 mSource = source; 54 mTitle = title; 55 mTags = null; 56 57 if (dest != null) { 58 int len = dest.length(); 59 if (len > 1 && dest.charAt(len - 1) != '/') { 60 mDest = dest + '/'; 61 } else { 62 mDest = dest; 63 } 64 } 65 } 66 67 /** 68 * Iterates a given sample code project gathering metadata for files and building 69 * a node tree that reflects the project's directory structure. After iterating 70 * the project, this method adds the project's metadata to jd_lists_unified, 71 * so that it is accessible for dynamic content and search suggestions. 72 * 73 * @param offlineMode Ignored -- offline-docs mode is not currently supported for 74 * browsable sample code projects. 75 * @return A root Node for the project containing its metadata and tree structure. 76 */ 77 public Node setSamplesTOC(boolean offlineMode) { 78 List<Node> filelist = new ArrayList<Node>(); 79 File f = new File(mSource); 80 mProjectDir = f.getName(); 81 String name = mProjectDir; 82 String mOut = mDest + name; 83 if (!f.isDirectory()) { 84 System.out.println("-samplecode not a directory: " + mSource); 85 return null; 86 } 87 88 Data hdf = Doclava.makeHDF(); 89 setProjectStructure(filelist, f, mDest); 90 String link = ClearPage.toroot + "samples/" + name + "/index" + Doclava.htmlExtension; 91 Node rootNode = writeSampleIndexCs(hdf, f, 92 new Node.Builder().setLabel(mProjectDir).setLink(link).setChildren(filelist).build(),false); 93 return rootNode; 94 } 95 96 /** 97 * For a given sample code project dir, iterate through the project generating 98 * browsable html for all valid sample code files. After iterating the project 99 * generate a templated index file to the project output root. 100 * 101 * @param offlineMode Ignored -- offline-docs mode is not currently supported for 102 * browsable sample code projects. 103 */ 104 public void writeSamplesFiles(boolean offlineMode) { 105 List<Node> filelist = new ArrayList<Node>(); 106 File f = new File(mSource); 107 mProjectDir = f.getName(); 108 String name = mProjectDir; 109 String mOut = mDest + name; 110 if (!f.isDirectory()) { 111 System.out.println("-samplecode not a directory: " + mSource); 112 } 113 114 Data hdf = Doclava.makeHDF(); 115 if (Doclava.samplesNavTree != null) { 116 hdf.setValue("samples_toc_tree", Doclava.samplesNavTree.getValue("samples_toc_tree", "")); 117 } 118 hdf.setValue("samples", "true"); 119 hdf.setValue("projectDir", mProjectDir); 120 writeProjectDirectory(f, mDest, false, hdf, "Files."); 121 writeProjectStructure(name, hdf); 122 hdf.removeTree("parentdirs"); 123 hdf.setValue("parentdirs.0.Name", name); 124 boolean writeFiles = true; 125 String link = "samples/" + name + "/index" + Doclava.htmlExtension; 126 //Write root _index.jd to out and add metadata to Node. 127 writeSampleIndexCs(hdf, f, 128 new Node.Builder().setLabel(mProjectDir).setLink(link).build(), true); 129 } 130 131 /** 132 * Given the root Node for a sample code project, iterates through the project 133 * gathering metadata and project tree structure. Unsupported file types are 134 * filtered from the project output. The collected project Nodes are appended to 135 * the root project node. 136 * 137 * @param parent The root Node that represents this sample code project. 138 * @param dir The current dir being processed. 139 * @param relative Relative path for creating links to this file. 140 */ 141 public void setProjectStructure(List<Node> parent, File dir, String relative) { 142 String name, link; 143 File[] dirContents = dir.listFiles(); 144 Arrays.sort(dirContents, byTypeAndName); 145 for (File f: dirContents) { 146 name = f.getName(); 147 if (!isValidFiletype(name)) { 148 continue; 149 } 150 if (f.isFile() && name.contains(".")) { 151 String path = relative + name; 152 link = convertExtension(path, Doclava.htmlExtension); 153 if (inList(path, IMAGES) || inList(path, VIDEOS) || inList(path, TEMPLATED)) { 154 parent.add(new Node.Builder().setLabel(name).setLink(ClearPage.toroot + link).build()); 155 } 156 } else if (f.isDirectory()) { 157 List<Node> mchildren = new ArrayList<Node>(); 158 String dirpath = relative + name + "/"; 159 setProjectStructure(mchildren, f, dirpath); 160 if (mchildren.size() > 0) { 161 parent.add(new Node.Builder().setLabel(name).setLink(ClearPage.toroot 162 + dirpath).setChildren(mchildren).build()); 163 } 164 } 165 } 166 } 167 168 /** 169 * Given a root sample code project path, iterates through the project 170 * setting page metadata to manage html output and writing/copying files to 171 * the output directory. Source files are templated and images are templated 172 * and linked to the original image. 173 * 174 * @param dir The current dir being processed. 175 * @param relative Relative path for creating links to this file. 176 * @param recursed Whether the method is being called recursively. 177 * @param hdf The data to read/write for files in this project. 178 * @param newKey Key passed in recursion for managing cs child trees. 179 */ 180 public void writeProjectDirectory(File dir, String relative, Boolean recursed, 181 Data hdf, String newkey) { 182 String name = ""; 183 String link = ""; 184 String type = ""; 185 int i = 0; 186 String expansion = ".Sub."; 187 String key = newkey; 188 189 if (recursed) { 190 key = (key + expansion); 191 } else { 192 expansion = ""; 193 } 194 195 File[] dirContents = dir.listFiles(); 196 Arrays.sort(dirContents, byTypeAndName); 197 for (File f: dirContents) { 198 name = f.getName(); 199 if (!isValidFiletype(name)) { 200 continue; 201 } 202 if (f.isFile() && name.contains(".")) { 203 String path = relative + name; 204 type = mapTypes(name); 205 link = convertExtension(path, Doclava.htmlExtension); 206 if (inList(path, IMAGES)) { 207 type = "img"; 208 if (f.length() < MAX_FILE_SIZE_BYTES) { 209 ClearPage.copyFile(false, f, path); 210 writeImageVideoPage(f, convertExtension(path, Doclava.htmlExtension), 211 relative, type, true); 212 } else { 213 writeImageVideoPage(f, convertExtension(path, Doclava.htmlExtension), 214 relative, type, false); 215 } 216 hdf.setValue(key + i + ".Type", "img"); 217 hdf.setValue(key + i + ".Name", name); 218 hdf.setValue(key + i + ".Href", link); 219 hdf.setValue(key + i + ".RelPath", relative); 220 } else if (inList(path, VIDEOS)) { 221 type = "video"; 222 if (f.length() < MAX_FILE_SIZE_BYTES) { 223 ClearPage.copyFile(false, f, path); 224 writeImageVideoPage(f, convertExtension(path, Doclava.htmlExtension), 225 relative, type, true); 226 } else { 227 writeImageVideoPage(f, convertExtension(path, Doclava.htmlExtension), 228 relative, type, false); 229 } 230 hdf.setValue(key + i + ".Type", "video"); 231 hdf.setValue(key + i + ".Name", name); 232 hdf.setValue(key + i + ".Href", link); 233 hdf.setValue(key + i + ".RelPath", relative); 234 } else if (inList(path, TEMPLATED)) { 235 writePage(f, convertExtension(path, Doclava.htmlExtension), relative, hdf); 236 hdf.setValue(key + i + ".Type", type); 237 hdf.setValue(key + i + ".Name", name); 238 hdf.setValue(key + i + ".Href", link); 239 hdf.setValue(key + i + ".RelPath", relative); 240 } 241 i++; 242 } else if (f.isDirectory()) { 243 List<Node> mchildren = new ArrayList<Node>(); 244 type = "dir"; 245 String dirpath = relative + name; 246 link = dirpath + "/index" + Doclava.htmlExtension; 247 String hdfkeyName = (key + i + ".Name"); 248 String hdfkeyType = (key + i + ".Type"); 249 String hdfkeyHref = (key + i + ".Href"); 250 hdf.setValue(hdfkeyName, name); 251 hdf.setValue(hdfkeyType, type); 252 hdf.setValue(hdfkeyHref, relative + name + "/" + "index" + Doclava.htmlExtension); 253 writeProjectDirectory(f, relative + name + "/", true, hdf, (key + i)); 254 i++; 255 } 256 } 257 258 setParentDirs(hdf, relative, name, false); 259 //Generate an index.html page for each dir being processed 260 if (FULL_TREE_NAVIGATION) { 261 ClearPage.write(hdf, "sampleindex.cs", relative + "/index" + Doclava.htmlExtension); 262 } 263 } 264 265 /** 266 * Processes a templated project index page from _index.jd in a project root. 267 * Each sample project must have an index, and each index locally defines it's own 268 * page.tags and sample.group cs vars. This method takes a SC node on input, reads 269 * any local vars from the _index.jd, optionally generates an html file to out, 270 * then updates the SC node with the page vars and returns it to the caller. 271 * 272 * @param hdf The data source to read/write for this index file. 273 * @param dir The sample project root directory. 274 * @param tnode A Node to serve as the project's root node. 275 * @param writeFiles If true, generates output files only. If false, collects 276 * metadata only. 277 * @return The tnode root with any metadata/child Nodes appended. 278 */ 279 public Node writeSampleIndexCs(Data hdf, File dir, Node tnode, boolean writeFiles) { 280 281 String filename = dir.getAbsolutePath() + "/_index.jd"; 282 String mGroup = ""; 283 File f = new File(filename); 284 String rel = dir.getPath(); 285 if (writeFiles) { 286 287 hdf.setValue("samples", "true"); 288 //set any default page variables for root index 289 hdf.setValue("page.title", mProjectDir); 290 hdf.setValue("projectDir", mProjectDir); 291 hdf.setValue("projectTitle", mTitle); 292 //add the download/project links to the landing pages. 293 hdf.setValue("samplesProjectIndex", "true"); 294 if (!f.isFile()) { 295 //The directory didn't have an _index.jd, so create a stub. 296 ClearPage.write(hdf, "sampleindex.cs", mDest + "index" + Doclava.htmlExtension); 297 } else { 298 DocFile.writePage(filename, rel, mDest + "index" + Doclava.htmlExtension, hdf); 299 } 300 } else if (f.isFile()) { 301 //gather metadata for toc and jd_lists_unified 302 DocFile.getPageMetadata(filename, hdf); 303 mGroup = hdf.getValue("sample.group", ""); 304 if (!"".equals(mGroup)) { 305 tnode.setGroup(hdf.getValue("sample.group", "")); 306 } else { 307 //Errors.error(Errors.INVALID_SAMPLE_INDEX, null, "Sample " + mProjectDir 308 // + ": Root _index.jd must be present and must define sample.group" 309 // + " tag. Please see ... for details."); 310 } 311 } 312 return tnode; 313 } 314 315 /** 316 * Sets metadata for managing html output and generates the project view page 317 * for a project. 318 * 319 * @param dir The project root dir. 320 * @param hdf The data to read/write for files in this project. 321 */ 322 public void writeProjectStructure(String dir, Data hdf) { 323 hdf.setValue("projectStructure", "true"); 324 hdf.setValue("projectDir", mProjectDir); 325 hdf.setValue("page.title", mProjectDir + " Structure"); 326 hdf.setValue("projectTitle", mTitle); 327 ClearPage.write(hdf, "sampleindex.cs", mDest + "project" + Doclava.htmlExtension); 328 hdf.setValue("projectStructure", ""); 329 } 330 331 /** 332 * Keeps track of each file's parent dirs. Used for generating path breadcrumbs in html. 333 * 334 * @param dir The data to read/write for this file. 335 * @param hdf The relative path for this file, from samples root. 336 * @param subdir The relative path for this file, from samples root. 337 * @param name The name of the file (minus extension). 338 * @param isFile Whether this is a file (not a dir). 339 */ 340 Data setParentDirs(Data hdf, String subdir, String name, Boolean isFile) { 341 if (FULL_TREE_NAVIGATION) { 342 hdf.setValue("linkfyPathCrumb", ""); 343 } 344 int iter; 345 hdf.removeTree("parentdirs"); 346 String s = subdir; 347 String urlParts[] = s.split("/"); 348 int n, l = 1; 349 for (iter=1; iter < urlParts.length; iter++) { 350 n = iter-1; 351 hdf.setValue("parentdirs." + n + ".Name", urlParts[iter]); 352 hdf.setValue("parentdirs." + n + ".Link", subdir + "index" + Doclava.htmlExtension); 353 } 354 return hdf; 355 } 356 357 /** 358 * Writes a templated source code file to out. 359 */ 360 public void writePage(File f, String out, String subdir, Data hdf) { 361 String name = f.getName(); 362 String path = f.getPath(); 363 String data = SampleTagInfo.readFile(new SourcePositionInfo(path, -1, -1), path, 364 "sample code", true, true, true, true); 365 data = Doclava.escape(data); 366 367 String relative = subdir.replaceFirst("samples/", ""); 368 setParentDirs(hdf, subdir, name, true); 369 hdf.setValue("projectTitle", mTitle); 370 hdf.setValue("projectDir", mProjectDir); 371 hdf.setValue("page.title", name); 372 hdf.setValue("subdir", subdir); 373 hdf.setValue("relative", relative); 374 hdf.setValue("realFile", name); 375 hdf.setValue("fileContents", data); 376 hdf.setValue("resTag", "sample"); 377 378 ClearPage.write(hdf, "sample.cs", out); 379 } 380 381 /** 382 * Writes a templated image or video file to out. 383 */ 384 public void writeImageVideoPage(File f, String out, String subdir, 385 String resourceType, boolean browsable) { 386 Data hdf = Doclava.makeHDF(); 387 if (Doclava.samplesNavTree != null) { 388 hdf.setValue("samples_toc_tree", Doclava.samplesNavTree.getValue("samples_toc_tree", "")); 389 } 390 hdf.setValue("samples", "true"); 391 392 String name = f.getName(); 393 if (!browsable) { 394 hdf.setValue("noDisplay", "true"); 395 } 396 setParentDirs(hdf, subdir, name, true); 397 hdf.setValue("samples", "true"); 398 hdf.setValue("page.title", name); 399 hdf.setValue("projectTitle", mTitle); 400 hdf.setValue("projectDir", mProjectDir); 401 hdf.setValue("subdir", subdir); 402 hdf.setValue("resType", resourceType); 403 hdf.setValue("realFile", name); 404 405 ClearPage.write(hdf, "sample.cs", out); 406 } 407 408 /** 409 * Given a node containing sample code projects and a node containing all valid 410 * group nodes, extract project nodes from tnode and append them to the group node 411 * that matches their sample.group metadata. 412 * 413 * @param tnode A list of nodes containing sample code projects. 414 * @param groupnodes A list of nodes that represent the valid sample groups. 415 * @return The groupnodes list with all projects appended properly to their 416 * associated sample groups. 417 */ 418 public static void writeSamplesNavTree(List<Node> tnode, List<Node> groupnodes) { 419 420 Node node = new Node.Builder().setLabel("Samples").setLink(ClearPage.toroot 421 + "samples/index" + Doclava.htmlExtension).setChildren(tnode).build(); 422 423 if (groupnodes != null) { 424 for (int i = 0; i < tnode.size(); i++) { 425 if (tnode.get(i) != null) { 426 groupnodes = appendNodeGroups(tnode.get(i), groupnodes); 427 } 428 } 429 for (int n = 0; n < groupnodes.size(); n++) { 430 if (groupnodes.get(n).getChildren() == null) { 431 groupnodes.remove(n); 432 n--; 433 } else { 434 Collections.sort(groupnodes.get(n).getChildren(), byLabel); 435 } 436 } 437 node.setChildren(groupnodes); 438 } 439 440 StringBuilder buf = new StringBuilder(); 441 node.renderGroupNodesTOC(buf); 442 if (Doclava.samplesNavTree != null) { 443 Doclava.samplesNavTree.setValue("samples_toc_tree", buf.toString()); 444 } 445 446 } 447 448 /** 449 * For a given project root node, get the group and then iterate the list of valid 450 * groups looking for a match. If found, append the project to that group node. 451 * Samples that reference a valid sample group tag are added to a list for that 452 * group. Samples declare a sample.group tag in their _index.jd files. 453 */ 454 private static List<Node> appendNodeGroups(Node gNode, List<Node> groupnodes) { 455 List<Node> mgrouplist = new ArrayList<Node>(); 456 for (int i = 0; i < groupnodes.size(); i++) { 457 if (gNode.getGroup().equals(groupnodes.get(i).getLabel())) { 458 if (groupnodes.get(i).getChildren() == null) { 459 mgrouplist.add(gNode); 460 groupnodes.get(i).setChildren(mgrouplist); 461 } else { 462 groupnodes.get(i).getChildren().add(gNode); 463 } 464 break; 465 } 466 } 467 return groupnodes; 468 } 469 470 /** 471 * Sorts an array of files by type and name (alpha), with manifest always at top. 472 */ 473 Comparator<File> byTypeAndName = new Comparator<File>() { 474 public int compare (File one, File other) { 475 if (one.isDirectory() && !other.isDirectory()) { 476 return 1; 477 } else if (!one.isDirectory() && other.isDirectory()) { 478 return -1; 479 } else if ("AndroidManifest.xml".equals(one.getName())) { 480 return -1; 481 } else { 482 return one.compareTo(other); 483 } 484 } 485 }; 486 487 /** 488 * Sorts a list of Nodes by label. 489 */ 490 public static Comparator<Node> byLabel = new Comparator<Node>() { 491 public int compare(Node one, Node other) { 492 return one.getLabel().compareTo(other.getLabel()); 493 } 494 }; 495 496 /** 497 * Concatenates dirs that only hold dirs, to simplify nav tree 498 */ 499 public static List<Node> squashNodes(List<Node> tnode) { 500 List<Node> list = tnode; 501 502 for(int i = 0; i < list.size(); ++i) { 503 if (("dir".equals(list.get(i).getType())) && 504 (list.size() == 1) && 505 (list.get(i).getChildren().get(0).getChildren() != null)) { 506 String thisLabel = list.get(i).getLabel(); 507 String childLabel = list.get(i).getChildren().get(0).getLabel(); 508 String newLabel = thisLabel + "/" + childLabel; 509 list.get(i).setLabel(newLabel); 510 list.get(i).setChildren(list.get(i).getChildren().get(0).getChildren()); 511 } else { 512 continue; 513 } 514 } 515 return list; 516 } 517 518 public static String convertExtension(String s, String ext) { 519 return s.substring(0, s.lastIndexOf('.')) + ext; 520 } 521 522 /** 523 * Whitelists of valid image/video and source code types. 524 */ 525 public static String[] IMAGES = {".png", ".jpg", ".gif"}; 526 public static String[] VIDEOS = {".mp4", ".ogv", ".webm"}; 527 public static String[] TEMPLATED = {".java", ".xml", ".aidl", ".rs",".txt", ".TXT"}; 528 529 public static boolean inList(String s, String[] list) { 530 for (String t : list) { 531 if (s.endsWith(t)) { 532 return true; 533 } 534 } 535 return false; 536 } 537 538 /** 539 * Maps filenames to a set of generic types. Used for displaying files/dirs 540 * in the project view page. 541 */ 542 public static String mapTypes(String name) { 543 String type = name.substring(name.lastIndexOf('.') + 1, name.length()); 544 if ("xml".equals(type) || "java".equals(type)) { 545 if ("AndroidManifest.xml".equals(name)) type = "manifest"; 546 return type; 547 } else { 548 return type = "file"; 549 } 550 } 551 552 /** 553 * Validates a source file from a project against restrictions to determine 554 * whether to include the file in the browsable project output. 555 */ 556 public boolean isValidFiletype(String name) { 557 if (name.startsWith(".") || 558 name.startsWith("_") || 559 "default.properties".equals(name) || 560 "build.properties".equals(name) || 561 name.endsWith(".ttf") || 562 name.endsWith(".gradle") || 563 name.endsWith(".bat") || 564 "Android.mk".equals(name)) { 565 return false; 566 } else { 567 return true; 568 } 569 } 570 571 /** 572 * SampleCode variant of NavTree node. 573 */ 574 public static class Node { 575 private String mLabel; 576 private String mLink; 577 private String mGroup; // from sample.group in _index.jd 578 private List<Node> mChildren; 579 private String mType; 580 581 private Node(Builder builder) { 582 mLabel = builder.mLabel; 583 mLink = builder.mLink; 584 mGroup = builder.mGroup; 585 mChildren = builder.mChildren; 586 mType = builder.mType; 587 } 588 589 public static class Builder { 590 private String mLabel, mLink, mGroup, mType; 591 private List<Node> mChildren = null; 592 public Builder setLabel(String mLabel) { this.mLabel = mLabel; return this;} 593 public Builder setLink(String mLink) { this.mLink = mLink; return this;} 594 public Builder setGroup(String mGroup) { this.mGroup = mGroup; return this;} 595 public Builder setChildren(List<Node> mChildren) { this.mChildren = mChildren; return this;} 596 public Builder setType(String mType) { this.mType = mType; return this;} 597 public Node build() {return new Node(this);} 598 } 599 600 /** 601 * Renders browsable sample groups and projects to an html list, starting 602 * from the group nodes and then rendering their project nodes and finally their 603 * child dirs and files. 604 */ 605 void renderGroupNodesTOC(StringBuilder buf) { 606 List<Node> list = mChildren; 607 if (list == null || list.size() == 0) { 608 return; 609 } else { 610 final int n = list.size(); 611 for (int i = 0; i < n; i++) { 612 if (list.get(i).getChildren() == null) { 613 continue; 614 } else { 615 buf.append("<li class=\"nav-section\">"); 616 buf.append("<div class=\"nav-section-header\">"); 617 buf.append("<a href=\"" + list.get(i).getLink() + "\" title=\"" 618 + list.get(i).getLabel() + "\">" 619 + list.get(i).getLabel() + "</a>"); 620 buf.append("</div>"); 621 buf.append("<ul>"); 622 list.get(i).renderProjectNodesTOC(buf); 623 } 624 } 625 buf.append("</ul>"); 626 buf.append("</li>"); 627 } 628 } 629 630 /** 631 * Renders a list of sample code projects associated with a group node. 632 */ 633 void renderProjectNodesTOC(StringBuilder buf) { 634 List<Node> list = mChildren; 635 if (list == null || list.size() == 0) { 636 return; 637 } else { 638 final int n = list.size(); 639 for (int i = 0; i < n; i++) { 640 if (list.get(i).getChildren() == null) { 641 continue; 642 } else { 643 buf.append("<li class=\"nav-section\">"); 644 buf.append("<div class=\"nav-section-header\">"); 645 buf.append("<a href=\"" + list.get(i).getLink() + "\" title=\"" 646 + list.get(i).getLabel() + "\">" 647 + list.get(i).getLabel() + "</a>"); 648 buf.append("</div>"); 649 buf.append("<ul>"); 650 list.get(i).renderChildrenToc(buf); 651 } 652 } 653 buf.append("</ul>"); 654 buf.append("</li>"); 655 } 656 } 657 658 /** 659 * Renders child dirs and files associated with a project node. 660 */ 661 void renderChildrenToc(StringBuilder buf) { 662 List<Node> list = mChildren; 663 if (list == null || list.size() == 0) { 664 buf.append("null"); 665 } else { 666 final int n = list.size(); 667 for (int i = 0; i < n; i++) { 668 if (list.get(i).getChildren() == null) { 669 buf.append("<li>"); 670 buf.append("<a href=\"" + list.get(i).getLink() + "\" title=\"" 671 + list.get(i).getLabel() + "\">" 672 + list.get(i).getLabel() + "</a>"); 673 buf.append(" </li>"); 674 } else { 675 buf.append("<li class=\"nav-section sticky\">"); 676 buf.append("<div class=\"nav-section-header empty\">"); 677 buf.append("<a href=\"#\" onclick=\"return false;\" title=\"" 678 + list.get(i).getLabel() + "\">" 679 + list.get(i).getLabel() + "/</a>"); 680 buf.append("</div>"); 681 buf.append("<ul>"); 682 list.get(i).renderChildrenToc(buf); 683 } 684 } 685 buf.append("</ul>"); 686 buf.append("</li>"); 687 } 688 } 689 690 /** 691 * Node getters and setters 692 */ 693 public String getLabel() { 694 return mLabel; 695 } 696 697 public void setLabel(String label) { 698 mLabel = label; 699 } 700 701 public String getLink() { 702 return mLink; 703 } 704 705 public void setLink(String ref) { 706 mLink = ref; 707 } 708 709 public String getGroup() { 710 return mGroup; 711 } 712 713 public void setGroup(String group) { 714 mGroup = group; 715 } 716 717 public List<Node> getChildren() { 718 return mChildren; 719 } 720 721 public void setChildren(List<Node> node) { 722 mChildren = node; 723 } 724 725 public String getType() { 726 return mType; 727 } 728 729 public void setType(String type) { 730 mType = type; 731 } 732 } 733 } 734