view src/nabble/naml/dom/ParserImpl.java @ 0:7ecd1a4ef557

add content
author Franklin Schmidt <fschmidt@gmail.com>
date Thu, 21 Mar 2019 19:15:52 -0600
parents
children
line wrap: on
line source

package nabble.naml.dom;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.ArrayList;


final class ParserImpl implements Parser {
	private static final Logger logger = LoggerFactory.getLogger(ParserImpl.class);

	private int startingLine = 0;
	private int i;
	private int iLine;
	private int line;
	private int len;
	private String text;
	private ParserImpl subParser = null;

	public Naml parse(String source) throws ParseException {
		i = 0;
		iLine = 0;
		line = startingLine;
		len = source.length();
		text = source;
		return doParse(null).naml;
	}

	private static class Return {
		final Naml naml;
		final String spaceAtEndOfClosingTag;

		Return(Naml naml,String spaceAtEndOfClosingTag) {
			this.naml = naml;
			this.spaceAtEndOfClosingTag = spaceAtEndOfClosingTag;
		}
	}

	private Return doParse(String tagName) throws ParseException {
		int start = i;
		Naml naml = new Naml();
		int i2;
		while( i < len ) {
			i2 = text.indexOf('<',i);
			if( i2 == -1 ) {
				if( tagName != null )
					throw parseException("unclosed tag: "+tagName,start);
				naml.add( text.substring(i).intern() );
				return new Return(naml,null);
			}
			if( i < i2 )
				naml.add( text.substring(i,i2).intern() );
			if( text.startsWith("<!--",i2) ) {
				i = text.indexOf("-->",i2);
				if( i < i2+4 )
					throw parseException("unclosed comment",i2);
				naml.add( new Comment( text.substring(i2+4,i) ) );
				i += 3;
			} else if( text.startsWith("<![CDATA[",i2) ) {
				i = text.indexOf("]]>",i2);
				if( i == -1 )
					throw parseException("unclosed CDATA",i2);
				naml.add( new Cdata( text.substring(i2+9,i) ) );
				i += 3;
			} else {
				i = text.indexOf('>',i2);
				if( i == -1 )
					throw parseException("unclosed tag",i2);
				setLine(i2);
				if( text.charAt(i2+1) == '/' ) {
					int nameStart = i2 + 2;
					int nameEnd = nameEnd(nameStart);
					int spaceEnd = skipSpace(nameEnd);
					if( spaceEnd != i )
						throw parseException("illegal char",spaceEnd);
					if( !text.substring(nameStart,nameEnd).equals(tagName) ) {
						if( tagName == null )
							throw parseException("unmatched closing tag: "+text.substring(i2,i+1),i2);
						else
							throw parseException("closing tag "+text.substring(i2,i+1)+" doesn't match expected </"+tagName+">",i2);
					}
					i++;
					return new Return(naml,text.substring(nameEnd,spaceEnd));
				}
				int startLine = line;
				if( text.charAt(i-1) == '/' ) {
					TagInfo tagInfo = new TagInfo(i2+1,i-1);
					if( tagInfo.name.endsWithDot() )
						throw parseException("empty tag can't end with dot: "+tagInfo.tagName,i);
					naml.add( new EmptyElement(tagInfo.name,tagInfo.attributes,tagInfo.spaceAtEnd,startLine) );
					i++;
				} else {
					TagInfo tagInfo = new TagInfo(i2+1,i);
					i++;
					String s = tagInfo.tagName.toLowerCase();
					Return r = doParse(tagInfo.tagName);
					naml.add( new Container(tagInfo.name,tagInfo.attributes,tagInfo.spaceAtEnd,startLine,r.naml,r.spaceAtEndOfClosingTag) );
				}
			}
		}
		if( tagName != null )
			throw parseException("unclosed tag: "+tagName,start);
		return new Return(naml,null);
	}

	private class TagInfo {
		final String tagName;
		final ElementName name;
		final List<Attribute> attributes = new ArrayList<Attribute>();
		final String spaceAtEnd;

		TagInfo(int start,int end) throws ParseException {
			int i = nameEnd(start);
			tagName = text.substring(start,i);
			name = new ElementName(tagName);
			int afterSpace;
			while( (afterSpace = skipSpace(i)) < end ) {
				int afterName = nameEnd(afterSpace);
				int afterSpaceAfterName = skipSpace(afterName);
				if( text.charAt(afterSpaceAfterName) != '=' )
					throw parseException("expected '=' not found for attribute '"+text.substring(afterSpace,afterName)+"'",afterSpaceAfterName);
				int beforeSpaceBeforeValue = afterSpaceAfterName + 1;
				int afterSpaceBeforeValue = skipSpace(beforeSpaceBeforeValue);
				char quote = text.charAt(afterSpaceBeforeValue);
				if( quote != '"' && quote != '\'' )
					throw parseException("missing quote",afterSpaceBeforeValue);
				int startValue = afterSpaceBeforeValue + 1; 
				int endValue = startValue; 
				while(true) {
					if( endValue == end )
						throw parseException("missing closing quote",endValue);
					char c = text.charAt(endValue);
					if( c == quote )
						break;
					if( c == '<' || c == '>' )
						throw parseException("invalid quoted char '"+c+"'",endValue);
					endValue++;
				}
				String spaceBeforeName = text.substring(i,afterSpace);
				String name = text.substring(afterSpace,afterName);
				String spaceAfterName = text.substring(afterName,afterSpaceAfterName);
				String spaceBeforeValue = text.substring(beforeSpaceBeforeValue,afterSpaceBeforeValue);
				String valueStr = text.substring(startValue,endValue);
				Naml value;
				if( quote == '"' ) {
					setLine(startValue);
					ParserImpl parser = parser();
					String source = valueStr.replace('[','<').replace(']','>');
					value = parser.parse(source);
				} else {
					value = new Naml();
					value.add(valueStr);
				}
				attributes.add( new Attribute(spaceBeforeName,name,spaceAfterName,spaceBeforeValue,value,quote) );
				i = endValue + 1;
			}
			spaceAtEnd = text.substring(i,end);
		}
	}

	private ParserImpl parser() {
		if( subParser == null )
			subParser = new ParserImpl();
		subParser.startingLine = line;
		return subParser;
	}

	private int skipSpace(int i) {
		while( i<len && Character.isWhitespace(text.charAt(i)) )
			i++;
		return i;
	}

	private int nameEnd(int i) throws ParseException {
		if( i >= len )
			throw parseException("name not found",i);
		char c = text.charAt(i);
		if( !( Character.isLetter(c) || c=='_' ) )
			throw parseException("invalid char: '"+c+"'",i);
		while( ++i < len ) {
			c = text.charAt(i);
			if( !( Character.isLetterOrDigit(c) || c=='_' || c=='.' || c=='-' || c==':' ) )
				break;
		}
		return i;
	}

	private void setLine(int i) {
		line += lines(iLine,i);
		iLine = i;
	}

	private int lines(int start,int end) {
		int n = 0;
		int i = start - 1;
		while(true) {
			i = text.indexOf('\n',i+1);
			if( i == -1 || i >= end )
				return n;
			n++;
		}
	}

	private ParseException parseException(String msg,int i) {
		return new ParseException(msg,startingLine+lines(0,i));
	}

}