All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.rjeschke.txtmark.Line Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2011 René Jeschke 
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.rjeschke.txtmark;

import java.util.LinkedList;

/**
 * This class represents a text line.
 * 
 * 

* It also provides methods for processing and analyzing a line. *

* * @author René Jeschke */ class Line { /** Current cursor position. */ public int pos; /** Leading and trailing spaces. */ public int leading = 0, trailing = 0; /** Is this line empty? */ public boolean isEmpty = true; /** This line's value. */ public String value = null; /** Previous and next line. */ public Line previous = null, next = null; /** Is previous/next line empty? */ public boolean prevEmpty, nextEmpty; /** Final line of a XML block. */ public Line xmlEndLine; /** Constructor. */ public Line() { // } /** * Calculates leading and trailing spaces. Also sets empty if needed. */ public void init() { this.leading = 0; while(this.leading < this.value.length() && this.value.charAt(this.leading) == ' ') this.leading++; if(this.leading == this.value.length()) { this.setEmpty(); } else { this.isEmpty = false; this.trailing = 0; while(this.value.charAt(this.value.length() - this.trailing - 1) == ' ') this.trailing++; } } /** * Recalculate leading spaces. */ public void initLeading() { this.leading = 0; while(this.leading < this.value.length() && this.value.charAt(this.leading) == ' ') this.leading++; if(this.leading == this.value.length()) { this.setEmpty(); } } /** * Skips spaces. * * @return false if end of line is reached */ // TODO use Util#skipSpaces public boolean skipSpaces() { while(this.pos < this.value.length() && this.value.charAt(this.pos) == ' ') this.pos++; return this.pos < this.value.length(); } /** * Reads chars from this line until any 'end' char is reached. * * @param end * Delimiting character(s) * @return The read String or null if no 'end' char was * reached. */ // TODO use Util#readUntil public String readUntil(char... end) { final StringBuilder sb = new StringBuilder(); int pos = this.pos; while(pos < this.value.length()) { final char ch = this.value.charAt(pos); if(ch == '\\' && pos + 1 < this.value.length()) { final char c; switch(c = this.value.charAt(pos + 1)) { case '\\': case '[': case ']': case '(': case ')': case '{': case '}': case '#': case '"': case '\'': case '.': case '>': case '*': case '+': case '-': case '_': case '!': case '`': sb.append(c); pos++; break; default: sb.append(ch); break; } } else { boolean endReached = false; for(int n = 0; n < end.length; n++) { if(ch == end[n]) { endReached = true; break; } } if(endReached) break; sb.append(ch); } pos++; } final char ch = pos < this.value.length() ? this.value.charAt(pos) : '\n'; for(int n = 0; n < end.length; n++) { if(ch == end[n]) { this.pos = pos; return sb.toString(); } } return null; } /** * Marks this line empty. Also sets previous/next line's empty attributes. */ public void setEmpty() { this.value = ""; this.leading = this.trailing = 0; this.isEmpty = true; if(this.previous != null) this.previous.nextEmpty = true; if(this.next != null) this.next.prevEmpty = true; } /** * Counts the amount of 'ch' in this line. * * @param ch * The char to count. * @return A value > 0 if this line only consists of 'ch' end spaces. */ private int countChars(char ch) { int count = 0; for(int i = 0; i < this.value.length(); i++) { final char c = this.value.charAt(i); if(c == ' ') continue; if(c == ch) { count++; continue; } count = 0; break; } return count; } /** * Counts the amount of 'ch' at the start of this line ignoring spaces. * * @param ch * The char to count. * @return Number of characters found. * @since 0.7 */ private int countCharsStart(char ch) { int count = 0; for(int i = 0; i < this.value.length(); i++) { final char c = this.value.charAt(i); if(c == ' ') continue; if(c == ch) count++; else break; } return count; } /** * Gets this line's type. * * @param extendedMode * Whether extended profile is enabled or not * @return The LineType. */ public LineType getLineType(boolean extendedMode) { if(this.isEmpty) return LineType.EMPTY; if(this.leading > 3) return LineType.CODE; if(this.value.charAt(this.leading) == '#') return LineType.HEADLINE; if(this.value.charAt(this.leading) == '>') return LineType.BQUOTE; if(extendedMode) { if(this.value.length() - this.leading - this.trailing > 2 && (this.value.charAt(this.leading) == '`' || this.value.charAt(this.leading) == '~' || this.value.charAt(this.leading) == '%')) { if(this.countCharsStart('`') >= 3) return LineType.FENCED_CODE; if(this.countCharsStart('~') >= 3) return LineType.FENCED_CODE; if(this.countCharsStart('%') >= 3) return LineType.PLUGIN; } } if(this.value.length() - this.leading - this.trailing > 2 && (this.value.charAt(this.leading) == '*' || this.value.charAt(this.leading) == '-' || this.value .charAt(this.leading) == '_')) { if(this.countChars(this.value.charAt(this.leading)) >= 3) return LineType.HR; } if(this.value.length() - this.leading >= 2 && this.value.charAt(this.leading + 1) == ' ') { switch(this.value.charAt(this.leading)) { case '*': case '-': case '+': return LineType.ULIST; } } if(this.value.length() - this.leading >= 3 && Character.isDigit(this.value.charAt(this.leading))) { int i = this.leading + 1; while(i < this.value.length() && Character.isDigit(this.value.charAt(i))) i++; if(i + 1 < this.value.length() && this.value.charAt(i) == '.' && this.value.charAt(i + 1) == ' ') return LineType.OLIST; } if(this.value.charAt(this.leading) == '<') { if(this.checkHTML()) return LineType.XML; } if(this.next != null && !this.next.isEmpty) { if((this.next.value.charAt(0) == '-') && (this.next.countChars('-') > 0)) return LineType.HEADLINE2; if((this.next.value.charAt(0) == '=') && (this.next.countChars('=') > 0)) return LineType.HEADLINE1; } return LineType.OTHER; } /** * Reads an XML comment. Sets xmlEndLine. * * @param firstLine * The Line to start reading from. * @param start * The starting position. * @return The new position or -1 if it is no valid comment. */ private int readXMLComment(final Line firstLine, final int start) { Line line = firstLine; if(start + 3 < line.value.length()) { if(line.value.charAt(2) == '-' && line.value.charAt(3) == '-') { int pos = start + 4; while(line != null) { while(pos < line.value.length() && line.value.charAt(pos) != '-') { pos++; } if(pos == line.value.length()) { line = line.next; pos = 0; } else { if(pos + 2 < line.value.length()) { if(line.value.charAt(pos + 1) == '-' && line.value.charAt(pos + 2) == '>') { this.xmlEndLine = line; return pos + 3; } } pos++; } } } } return -1; } /** * Checks if this line contains an ID at it's end and removes it from the * line. * * @return The ID or null if no valid ID exists. */ // FIXME ... hack public String stripID() { if(this.isEmpty || this.value.charAt(this.value.length() - this.trailing - 1) != '}') return null; int p = this.leading; boolean found = false; while(p < this.value.length() && !found) { switch(this.value.charAt(p)) { case '\\': if(p + 1 < this.value.length()) { switch(this.value.charAt(p + 1)) { case '{': p++; break; } } p++; break; case '{': found = true; break; default: p++; break; } } if(found) { if(p + 1 < this.value.length() && this.value.charAt(p + 1) == '#') { final int start = p + 2; p = start; found = false; while(p < this.value.length() && !found) { switch(this.value.charAt(p)) { case '\\': if(p + 1 < this.value.length()) { switch(this.value.charAt(p + 1)) { case '}': p++; break; } } p++; break; case '}': found = true; break; default: p++; break; } } if(found) { final String id = this.value.substring(start, p).trim(); if(this.leading != 0) { this.value = this.value.substring(0, this.leading) + this.value.substring(this.leading, start - 2).trim(); } else { this.value = this.value.substring(this.leading, start - 2).trim(); } this.trailing = 0; return id.length() > 0 ? id : null; } } } return null; } /** * Checks for a valid HTML block. Sets xmlEndLine. * * @return true if it is a valid block. */ private boolean checkHTML() { final LinkedList tags = new LinkedList(); final StringBuilder temp = new StringBuilder(); int pos = this.leading; if(this.value.charAt(this.leading + 1) == '!') { if(this.readXMLComment(this, this.leading) > 0) return true; } pos = Utils.readXML(temp, this.value, this.leading, false); String element, tag; if(pos > -1) { element = temp.toString(); temp.setLength(0); Utils.getXMLTag(temp, element); tag = temp.toString().toLowerCase(); if(!HTML.isHtmlBlockElement(tag)) return false; if(tag.equals("hr")) { this.xmlEndLine = this; return true; } tags.add(tag); Line line = this; while(line != null) { while(pos < line.value.length() && line.value.charAt(pos) != '<') { pos++; } if(pos >= line.value.length()) { line = line.next; pos = 0; } else { temp.setLength(0); final int newPos = Utils.readXML(temp, line.value, pos, false); if(newPos > 0) { element = temp.toString(); temp.setLength(0); Utils.getXMLTag(temp, element); tag = temp.toString().toLowerCase(); if(HTML.isHtmlBlockElement(tag) && !tag.equals("hr")) { if(element.charAt(1) == '/') { if(!tags.getLast().equals(tag)) return false; tags.removeLast(); } else { tags.addLast(tag); } } if(tags.size() == 0) { this.xmlEndLine = line; break; } pos = newPos; } else { pos++; } } } return tags.size() == 0; } return false; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy