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

com.phloc.html.markdown.Line Maven / Gradle / Ivy

There is a newer version: 4.4.9
Show newest version
/**
 * Copyright (C) 2006-2015 phloc systems
 * http://www.phloc.com
 * office[at]phloc[dot]com
 *
 * 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.phloc.html.markdown;

import java.util.LinkedList;

import javax.annotation.Nonnull;

import com.phloc.html.EHTMLElement;

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

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

* * @author René Jeschke <[email protected]> */ final class Line { /** Current cursor position. */ public int m_nPos; /** Leading spaces. */ public int m_nLeading = 0; /** Trailing spaces. */ public int m_nTrailing = 0; /** Is this line empty? */ public boolean m_bIsEmpty = true; /** This line's value. */ public String m_sValue = null; /** Previous line. */ public Line m_aPrevious = null; /** Next line. */ public Line m_aNext = null; /** Is previous line empty? */ public boolean m_bPrevEmpty; /** Final line of a XML block. */ public Line m_aXmlEndLine; /** Constructor. */ public Line () {} /** * Calculates leading and trailing spaces. Also sets empty if needed. */ public void init () { m_nLeading = 0; while (m_nLeading < m_sValue.length () && m_sValue.charAt (m_nLeading) == ' ') m_nLeading++; if (m_nLeading == m_sValue.length ()) { setEmpty (); } else { m_bIsEmpty = false; m_nTrailing = 0; while (m_sValue.charAt (m_sValue.length () - m_nTrailing - 1) == ' ') m_nTrailing++; } } /** * Recalculate leading spaces. */ public void initLeading () { m_nLeading = 0; while (m_nLeading < m_sValue.length () && m_sValue.charAt (m_nLeading) == ' ') m_nLeading++; if (m_nLeading == m_sValue.length ()) setEmpty (); } /** * Skips spaces. * * @return false if end of line is reached */ public boolean skipSpaces () { while (m_nPos < m_sValue.length () && m_sValue.charAt (m_nPos) == ' ') m_nPos++; return m_nPos < m_sValue.length (); } /** * Reads chars from this line until any 'end' char is reached. * * @param aEndChars * Delimiting character(s) * @return The read String or null if no 'end' char was reached. */ public String readUntil (final char... aEndChars) { final StringBuilder aSB = new StringBuilder (); int nPos = m_nPos; while (nPos < m_sValue.length ()) { final char ch = m_sValue.charAt (nPos); if (ch == '\\' && nPos + 1 < m_sValue.length ()) { final char c = m_sValue.charAt (nPos + 1); if (Utils.isEscapeChar (c)) { aSB.append (c); nPos++; } else aSB.append (ch); } else { boolean bEndReached = false; for (final char cElement : aEndChars) if (ch == cElement) { bEndReached = true; break; } if (bEndReached) break; aSB.append (ch); } nPos++; } final char ch = nPos < m_sValue.length () ? m_sValue.charAt (nPos) : '\n'; for (final char cElement : aEndChars) if (ch == cElement) { m_nPos = nPos; return aSB.toString (); } return null; } /** * Marks this line empty. Also sets previous/next line's empty attributes. */ public void setEmpty () { m_sValue = ""; m_nLeading = 0; m_nTrailing = 0; m_bIsEmpty = true; if (m_aNext != null) m_aNext.m_bPrevEmpty = 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 _countConsecutiveChars (final char ch) { int count = 0; for (int i = 0; i < m_sValue.length (); i++) { final char c = m_sValue.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 (final char ch) { int count = 0; for (int i = 0; i < m_sValue.length (); i++) { final char c = m_sValue.charAt (i); if (c == ' ') continue; if (c != ch) break; count++; } return count; } /** * Gets this line's type. * * @param bExtendedMode * Whether extended profile is enabled or not * @return The LineType. */ @Nonnull public ELineType getLineType (final boolean bExtendedMode) { if (m_bIsEmpty) return ELineType.EMPTY; if (m_nLeading > 3) return ELineType.CODE; if (m_sValue.charAt (m_nLeading) == '#') return ELineType.HEADLINE; if (m_sValue.charAt (m_nLeading) == '>') return ELineType.BQUOTE; if (bExtendedMode) { if (m_sValue.length () - m_nLeading - m_nTrailing > 2 && (m_sValue.charAt (m_nLeading) == '`' || m_sValue.charAt (m_nLeading) == '~' || m_sValue.charAt (m_nLeading) == '%')) { if (_countCharsStart ('`') >= 3) return ELineType.FENCED_CODE; if (_countCharsStart ('~') >= 3) return ELineType.FENCED_CODE; if (_countCharsStart ('%') >= 3) return ELineType.PLUGIN; } } if (m_sValue.length () - m_nLeading - m_nTrailing > 2 && (m_sValue.charAt (m_nLeading) == '*' || m_sValue.charAt (m_nLeading) == '-' || m_sValue.charAt (m_nLeading) == '_')) { if (_countConsecutiveChars (m_sValue.charAt (m_nLeading)) >= 3) return ELineType.HR; } if (m_sValue.length () - m_nLeading >= 2 && m_sValue.charAt (m_nLeading + 1) == ' ') { switch (m_sValue.charAt (m_nLeading)) { case '*': case '-': case '+': return ELineType.ULIST; } } if (m_sValue.length () - m_nLeading >= 3 && Character.isDigit (m_sValue.charAt (m_nLeading))) { int i = m_nLeading + 1; while (i < m_sValue.length () && Character.isDigit (m_sValue.charAt (i))) i++; if (i + 1 < m_sValue.length () && m_sValue.charAt (i) == '.' && m_sValue.charAt (i + 1) == ' ') return ELineType.OLIST; } if (m_sValue.charAt (m_nLeading) == '<') { final EHTMLElementType eType = _checkHTML (); if (eType == EHTMLElementType.TAG) return ELineType.XML; if (eType == EHTMLElementType.COMMENT) return ELineType.XML_COMMENT; } if (m_aNext != null && !m_aNext.m_bIsEmpty) { if (m_aNext.m_sValue.charAt (0) == '-' && m_aNext._countConsecutiveChars ('-') > 0) return ELineType.HEADLINE2; if (m_aNext.m_sValue.charAt (0) == '=' && m_aNext._countConsecutiveChars ('=') > 0) return ELineType.HEADLINE1; } return ELineType.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.m_sValue.length ()) { if (line.m_sValue.charAt (2) == '-' && line.m_sValue.charAt (3) == '-') { int pos = start + 4; while (line != null) { while (pos < line.m_sValue.length () && line.m_sValue.charAt (pos) != '-') { pos++; } if (pos == line.m_sValue.length ()) { line = line.m_aNext; pos = 0; } else { if (pos + 2 < line.m_sValue.length ()) { if (line.m_sValue.charAt (pos + 1) == '-' && line.m_sValue.charAt (pos + 2) == '>') { m_aXmlEndLine = 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 (m_bIsEmpty || m_sValue.charAt (m_sValue.length () - m_nTrailing - 1) != '}') return null; int nPos = m_nLeading; boolean bFound = false; while (nPos < m_sValue.length () && !bFound) { switch (m_sValue.charAt (nPos)) { case '\\': if (nPos + 1 < m_sValue.length ()) { if (m_sValue.charAt (nPos + 1) == '{') nPos++; } nPos++; break; case '{': bFound = true; break; default: nPos++; break; } } if (bFound) { if (nPos + 1 < m_sValue.length () && m_sValue.charAt (nPos + 1) == '#') { final int nStart = nPos + 2; nPos = nStart; bFound = false; while (nPos < m_sValue.length () && !bFound) { switch (m_sValue.charAt (nPos)) { case '\\': if (nPos + 1 < m_sValue.length ()) { if (m_sValue.charAt (nPos + 1) == '}') nPos++; } nPos++; break; case '}': bFound = true; break; default: nPos++; break; } } if (bFound) { final String sID = m_sValue.substring (nStart, nPos).trim (); if (m_nLeading != 0) { m_sValue = m_sValue.substring (0, m_nLeading) + m_sValue.substring (m_nLeading, nStart - 2).trim (); } else { m_sValue = m_sValue.substring (m_nLeading, nStart - 2).trim (); } m_nTrailing = 0; return sID.length () > 0 ? sID : null; } } } return null; } private static enum EHTMLElementType { NONE, TAG, COMMENT; } /** * Checks for a valid HTML block. Sets xmlEndLine. * * @return EHTMLType.TAG or EHTMLType.COMMENT if it * is a valid block. */ @Nonnull private EHTMLElementType _checkHTML () { final LinkedList aTags = new LinkedList (); final StringBuilder aSB = new StringBuilder (); int nPos = m_nLeading; if (m_sValue.charAt (m_nLeading + 1) == '!') { if (_readXMLComment (this, m_nLeading) > 0) return EHTMLElementType.COMMENT; } nPos = Utils.readXMLElement (aSB, m_sValue, m_nLeading, false); if (nPos > -1) { String sElement = aSB.toString (); String sTag = Utils.getXMLTag (sElement); if (!MarkdownHTML.isHtmlBlockElement (sTag)) return EHTMLElementType.NONE; if (EHTMLElement.getFromTagNameOrNull (sTag).mayBeSelfClosed ()) { m_aXmlEndLine = this; return EHTMLElementType.TAG; } aTags.add (sTag); Line aLine = this; while (aLine != null) { while (nPos < aLine.m_sValue.length () && aLine.m_sValue.charAt (nPos) != '<') nPos++; if (nPos >= aLine.m_sValue.length ()) { aLine = aLine.m_aNext; nPos = 0; } else { aSB.setLength (0); final int nNewPos = Utils.readXMLElement (aSB, aLine.m_sValue, nPos, false); if (nNewPos > 0) { sElement = aSB.toString (); sTag = Utils.getXMLTag (sElement); if (MarkdownHTML.isHtmlBlockElement (sTag) && !EHTMLElement.getFromTagNameOrNull (sTag).mayBeSelfClosed ()) { if (sElement.charAt (1) == '/') { if (!aTags.getLast ().equals (sTag)) return EHTMLElementType.NONE; aTags.removeLast (); } else { aTags.addLast (sTag); } } if (aTags.isEmpty ()) { m_aXmlEndLine = aLine; break; } nPos = nNewPos; } else { nPos++; } } } if (aTags.isEmpty ()) return EHTMLElementType.TAG; } return EHTMLElementType.NONE; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy