1 package eu.svjatoslav.sixth.core.document;
3 import eu.svjatoslav.commons.string.String2;
4 import eu.svjatoslav.commons.string.tokenizer.Tokenizer;
5 import eu.svjatoslav.commons.string.tokenizer.TokenizerMatch;
6 import eu.svjatoslav.sixth.core.document.content.ListElement;
7 import eu.svjatoslav.sixth.core.document.text.FormattedText;
9 import java.util.ArrayList;
10 import java.util.List;
12 import static eu.svjatoslav.sixth.core.document.Helper.TG_LIST;
14 public class Heading {
15 public final FormattedText name;
16 public final int level;
17 public final Heading parent;
18 private final List<Heading> children = new ArrayList<>();
20 public final ListElement rootListElement = new ListElement(null, -2, null, "");
21 private ListElement currentListElement = rootListElement;
23 public Heading(FormattedText name, int level, Heading parent){
29 public void addChild(Heading heading){
30 children.add(heading);
33 public List<Heading> getChildren(){
37 public String toMD () {
38 StringBuilder sb = new StringBuilder();
40 if (level > 0) sb.append(enlistTitleInMD());
42 rootListElement.toMD(sb, -2);
44 children.stream().map(Heading::toMD).forEach(sb::append);
49 private String enlistTitleInMD() {
50 String2 s = new String2();
51 s.append("#", level).append(" ").append(name.toMD(0)).append("\n");
55 public ListElement getCurrentHeading(){
56 return currentListElement;
59 public void parse(TokenizerMatch tm){
61 if (tm.isGroup(TG_LIST)){
66 currentListElement.parse(tm);
69 private void parseList(TokenizerMatch tm) {
70 String[] listSections = tm.getRegExpGroups();
71 int indent = listSections[0].length();
72 String type = listSections[1];
73 FormattedText title = FormattedText.fromOrg(parseFullListTitle(getPartialTitle(listSections), tm.getTokenizer(), indent));
75 if (indent > currentListElement.indent){
76 // list dept increases
77 ListElement newElement = new ListElement(title, indent, currentListElement, type);
78 currentListElement.addContent(newElement);
79 currentListElement = newElement;
83 if (indent == currentListElement.indent){
84 // list depth is the same
85 ListElement newElement = new ListElement(title, indent, currentListElement.parent, type);
86 currentListElement.parent.addContent(newElement);
87 currentListElement = newElement;
91 // list dept decreases
93 if (currentListElement.parent.indent <= indent){
94 if (currentListElement.parent.indent < 0){
95 // reached first depth level, cannot go any deeper.
96 // This special situation arisesbb only when lint indents are not properly aligned.
97 // That is, document structure is incorrect.
98 ListElement newElement = new ListElement(title, indent, currentListElement.parent, type);
99 currentListElement.parent.addContent(newElement);
100 currentListElement = newElement;
104 ListElement newElement = new ListElement(title, indent, currentListElement.parent.parent, type);
105 currentListElement.parent.parent.addContent(newElement);
106 currentListElement = newElement;
109 currentListElement = currentListElement.parent;
114 private String getPartialTitle(String[] listSections) {
115 return listSections.length > 2 ? listSections[2] : "";
118 private String parseFullListTitle(String partialTitle, Tokenizer tokenizer, int listIndent){
119 StringBuilder sb = new StringBuilder();
120 sb.append(partialTitle);
122 while (tokenizer.hasMoreContent()){
123 final TokenizerMatch tm = tokenizer.getNextToken();
125 if (isContentContinuation(tm, listIndent, Helper.TG_NORMAL_TEXT)){
126 String titleContinuation = tm.token.substring(listIndent).trim();
127 sb.append("\n").append(titleContinuation);
131 tokenizer.unreadToken();
135 return sb.toString();
138 public static boolean isContentContinuation(TokenizerMatch tm, int requiredIndent, String requiredGroup) {
139 if (tm.token.length() <= requiredIndent) return false;
141 return tm.isGroup(requiredGroup) && tm.token.substring(0, requiredIndent +1).trim().length() == 0;