1 module hunt.markdown.internal.util.Parsing; 2 import hunt.Char; 3 import hunt.util.StringBuilder; 4 import hunt.text.Common; 5 import hunt.logging; 6 alias Character = Char; 7 8 class Parsing { 9 10 private const string TAGNAME = "[A-Za-z][A-Za-z0-9-]*"; 11 private const string ATTRIBUTENAME = "[a-zA-Z_:][a-zA-Z0-9:._-]*"; 12 private const string UNQUOTEDVALUE = "[^\"'=<>`\\x00-\\x20]+"; 13 private const string SINGLEQUOTEDVALUE = "'[^']*'"; 14 private const string DOUBLEQUOTEDVALUE = "\"[^\"]*\""; 15 16 private const string ATTRIBUTEVALUE = "(?:" ~ UNQUOTEDVALUE ~ "|" ~ SINGLEQUOTEDVALUE ~ "|" ~ DOUBLEQUOTEDVALUE ~ ")"; 17 private const string ATTRIBUTEVALUESPEC = "(?:" ~ "\\s*=" ~ "\\s*" ~ ATTRIBUTEVALUE ~ ")"; 18 private const string ATTRIBUTE = "(?:" ~ "\\s+" ~ ATTRIBUTENAME ~ ATTRIBUTEVALUESPEC ~ "?)"; 19 20 public enum string OPENTAG = "<" ~ TAGNAME ~ ATTRIBUTE ~ "*" ~ "\\s*/?>"; 21 public enum string CLOSETAG = "</" ~ TAGNAME ~ "\\s*[>]"; 22 23 public enum int CODE_BLOCK_INDENT = 4; 24 25 public static int columnsToNextTabStop(int column) { 26 // Tab stop is 4 27 return 4 - (column % 4); 28 } 29 30 public static int find(char c, string s, int startIndex) { 31 int length = cast(int)(s.length); 32 for (int i = startIndex; i < length; i++) { 33 if (s[i] == c) { 34 return i; 35 } 36 } 37 return -1; 38 } 39 40 public static int findLineBreak(string s, int startIndex) { 41 int length = cast(int)(s.length); 42 for (int i = startIndex; i < length; i++) { 43 switch (s[i]) { 44 case '\n': 45 case '\r': 46 return i; 47 default:break; 48 } 49 } 50 return -1; 51 } 52 53 public static bool isBlank(string s) { 54 return findNonSpace(s, 0) == -1; 55 } 56 57 public static bool isLetter(string s, int index) { 58 // int codePoint = Char.codePointAt(s, index); 59 // return Char.isLetter(codePoint); 60 import std.ascii; 61 auto b = isAlpha(s.charAt(index)); 62 return b; 63 } 64 65 public static bool isSpaceOrTab(string s, int index) { 66 if (index < s.length) { 67 switch (s[index]) { 68 case ' ': 69 case '\t': 70 return true; 71 default: break; 72 } 73 } 74 return false; 75 } 76 77 /** 78 * Prepares the input line replacing {@code \0} 79 */ 80 public static string prepareLine(string line) { 81 // Avoid building a new string in the majority of cases (no \0) 82 StringBuilder sb = null; 83 int length = cast(int)(line.length); 84 for (int i = 0; i < length; i++) { 85 char c = line[i]; 86 switch (c) { 87 case '\0': 88 if (sb is null) { 89 sb = new StringBuilder(length); 90 sb.append(line, 0, i); 91 } 92 sb.append("\uFFFD"); 93 break; 94 default: 95 if (sb !is null) { 96 sb.append(c); 97 } 98 } 99 } 100 101 if (sb !is null) { 102 return sb.toString(); 103 } else { 104 return line; 105 } 106 } 107 108 public static int skip(char skip, string s, int startIndex, int endIndex) { 109 for (int i = startIndex; i < endIndex; i++) { 110 if (s[i] != skip) { 111 return i; 112 } 113 } 114 return endIndex; 115 } 116 117 public static int skipBackwards(char skip, string s, int startIndex, int lastIndex) { 118 for (int i = startIndex; i >= lastIndex; i--) { 119 if (s[i] != skip) { 120 return i; 121 } 122 } 123 return lastIndex - 1; 124 } 125 126 public static int skipSpaceTab(string s, int startIndex, int endIndex) { 127 for (int i = startIndex; i < endIndex; i++) { 128 switch (s[i]) { 129 case ' ': 130 case '\t': 131 break; 132 default: 133 return i; 134 } 135 } 136 return endIndex; 137 } 138 139 public static int skipSpaceTabBackwards(string s, int startIndex, int lastIndex) { 140 for (int i = startIndex; i >= lastIndex; i--) { 141 switch (s[i]) { 142 case ' ': 143 case '\t': 144 break; 145 default: 146 return i; 147 } 148 } 149 return lastIndex - 1; 150 } 151 152 private static int findNonSpace(string s, int startIndex) { 153 int length = cast(int)(s.length); 154 for (int i = startIndex; i < length; i++) { 155 switch (s[i]) { 156 case ' ': 157 case '\t': 158 case '\n': 159 case '\u000B': 160 case '\f': 161 case '\r': 162 break; 163 default: 164 return i; 165 } 166 } 167 return -1; 168 } 169 }