changeset 1466:670b7d089699

xml support
author Franklin Schmidt <fschmidt@gmail.com>
date Mon, 13 Apr 2020 22:00:40 -0600
parents 5e3870618377
children 509d49c493c0
files src/goodjava/xml/XmlElement.java src/goodjava/xml/XmlParser.java src/luan/modules/Parsers.luan src/luan/modules/parsers/Xml.java
diffstat 4 files changed, 334 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/goodjava/xml/XmlElement.java	Mon Apr 13 22:00:40 2020 -0600
@@ -0,0 +1,64 @@
+package goodjava.xml;
+
+import java.util.Map;
+
+
+public final class XmlElement {
+	public final String name;
+	public final Map<String,String> attributes;
+	public final Object content;
+
+	public XmlElement(String name,Map<String,String> attributes,String content) {
+		this.name = name;
+		this.attributes = attributes;
+		this.content = content;
+	}
+
+	public XmlElement(String name,Map<String,String> attributes,XmlElement[] content) {
+		this.name = name;
+		this.attributes = attributes;
+		this.content = content;
+	}
+
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		toString(sb,0);
+		return sb.toString();
+	}
+
+	private void toString(StringBuilder sb,int indented) {
+		indent(sb,indented);
+		sb.append( '<' );
+		sb.append( name );
+		for( Map.Entry<String,String> attribute : attributes.entrySet() ) {
+			sb.append( ' ' );
+			sb.append( attribute.getKey() );
+			sb.append( "=\"" );
+			sb.append( attribute.getValue() );
+			sb.append( '"' );
+		}
+		sb.append( '>' );
+		if( content instanceof String ) {
+			String s = (String)content;
+			sb.append(s);
+		} else if( content instanceof XmlElement[] ) {
+			XmlElement[] elements = (XmlElement[])content;
+			sb.append( '\n' );
+			for( XmlElement element : elements ) {
+				element.toString(sb,indented+1);
+			}
+			indent(sb,indented);
+		} else
+			throw new RuntimeException();
+		sb.append( "</" );
+		sb.append( name );
+		sb.append( ">\n" );
+	}
+
+	private void indent(StringBuilder sb,int indented) {
+		for( int i=0; i<indented; i++ ) {
+			sb.append('\t');
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/goodjava/xml/XmlParser.java	Mon Apr 13 22:00:40 2020 -0600
@@ -0,0 +1,172 @@
+package goodjava.xml;
+
+import java.util.Map;
+import java.util.AbstractMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.ArrayList;
+import goodjava.parser.Parser;
+import goodjava.parser.ParseException;
+
+
+public final class XmlParser {
+
+	public static XmlElement parse(String text) throws ParseException {
+		return new XmlParser(text).parse();
+	}
+
+	private final Parser parser;
+
+	private XmlParser(String text) {
+		this.parser = new Parser(text);
+	}
+
+	private ParseException exception(String msg) {
+		return new ParseException(parser,msg);
+	}
+
+	private XmlElement parse() throws ParseException {
+		spaces();
+		prolog();
+		spaces();
+		XmlElement element = element();
+		spaces();
+		if( !parser.endOfInput() )
+			throw exception("unexpected text");
+		return element;
+	}
+
+	private void prolog() throws ParseException {
+		if( !parser.match("<?xml") )
+			return;
+		while( attribute() != null );
+		spaces();
+		required("?>");
+	}
+
+	private XmlElement element() throws ParseException {
+		parser.begin();
+		if( !parser.match('<') || parser.test('/') )
+			return parser.failure(null);
+		//spaces();
+		String name = name();
+		if( name==null )
+			throw exception("element name not found");
+		Map<String,String> attributes = new LinkedHashMap<String,String>();
+		Map.Entry<String,String> attribute;
+		while( (attribute=attribute()) != null ) {
+			attributes.put(attribute.getKey(),attribute.getValue());
+		}
+		spaces();
+		required(">");
+		String s = string(name);
+		if( s != null ) {
+			XmlElement element = new XmlElement(name,attributes,s);
+			return parser.success(element);
+		}
+		List<XmlElement> elements = elements(name);
+		if( elements != null ) {
+			XmlElement element = new XmlElement(name,attributes,elements.toArray(new XmlElement[0]));
+			return parser.success(element);
+		}
+		throw exception("bad element");
+	}
+
+	private String string(String name) throws ParseException {
+		int start = parser.begin();
+		while( parser.noneOf("<") );
+		String s = parser.textFrom(start);
+		if( !endTag(name) )
+			return parser.failure(null);
+		return parser.success(s);
+	}
+
+	private List<XmlElement> elements(String name) throws ParseException {
+		parser.begin();
+		List<XmlElement> elements = new ArrayList<XmlElement>();
+		spaces();
+		XmlElement element;
+		while( (element=element()) != null ) {
+			elements.add(element);
+			spaces();
+		}
+		if( !endTag(name) )
+			return parser.failure(null);
+		return parser.success(elements);
+	}
+
+	private boolean endTag(String name) throws ParseException {
+		parser.begin();
+		if( !parser.match("</") || !parser.match(name) )
+			return parser.failure();
+		spaces();
+		if( !parser.match('>') )
+			return parser.failure();
+		return parser.success();
+	}
+
+	private Map.Entry<String,String> attribute() throws ParseException {
+		parser.begin();
+		if( !matchSpace() )
+			return parser.failure(null);
+		spaces();
+		String name = name();
+		if( name==null )
+			return parser.failure(null);
+		spaces();
+		required("=");
+		spaces();
+		if( !parser.anyOf("\"'") )
+			throw exception("quote expected");
+		char quote = parser.lastChar();
+		int start = parser.currentIndex();
+		while( !parser.test(quote) ) {
+			if( !parser.anyChar() )
+				throw exception("unclosed attribute value");
+		}
+		String value = parser.textFrom(start);
+		parser.match(quote);
+		Map.Entry<String,String> attribute = new AbstractMap.SimpleImmutableEntry<String,String>(name,value);
+		return parser.success(attribute);
+	}
+
+	private String name() {
+		int start = parser.currentIndex();
+		if( !matchNameChar() )
+			return null;
+		while( matchNameChar() );
+		return parser.textFrom(start);
+	}
+
+	private boolean matchNameChar() {
+		return parser.inCharRange('a','z')
+			|| parser.inCharRange('A','Z')
+			|| parser.inCharRange('0','9')
+			|| parser.anyOf("_.-:")
+		;
+	}
+
+	private void required(String s) throws ParseException {
+		if( !parser.match(s) )
+			exception("'"+s+"' expected");
+	}
+
+	private void spaces() throws ParseException {
+		while( matchSpace() || matchComment() );
+	}
+
+	private boolean matchComment() throws ParseException {
+		if( !parser.match("<!--") )
+			return false;
+		while( !parser.match("-->") ) {
+			if( !parser.anyChar() )
+				throw exception("unclosed comment");
+		}
+		return true;
+	}
+
+	private boolean matchSpace() {
+		return parser.anyOf(" \t\r\n");
+	}
+
+}
--- a/src/luan/modules/Parsers.luan	Sun Apr 12 15:59:57 2020 -0600
+++ b/src/luan/modules/Parsers.luan	Mon Apr 13 22:00:40 2020 -0600
@@ -2,6 +2,7 @@
 local BBCode = require "java:luan.modules.parsers.BBCode"
 local Csv = require "java:luan.modules.parsers.Csv"
 local Theme = require "java:luan.modules.parsers.Theme"
+local Xml = require "java:luan.modules.parsers.Xml"
 local BasicLuan = require "java:luan.modules.BasicLuan"
 
 
@@ -12,6 +13,8 @@
 Parsers.csv_to_list = Csv.toList
 Parsers.json_string = BasicLuan.json_string
 Parsers.theme_to_luan = Theme.toLuan
+Parsers.xml_parse = Xml.parse
+Parsers.xml_string = Xml.toString
 
 local Luan = require "luan:Luan.luan"
 local error = Luan.error
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/luan/modules/parsers/Xml.java	Mon Apr 13 22:00:40 2020 -0600
@@ -0,0 +1,95 @@
+package luan.modules.parsers;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.ArrayList;
+import goodjava.parser.ParseException;
+import goodjava.xml.XmlElement;
+import goodjava.xml.XmlParser;
+import luan.Luan;
+import luan.LuanTable;
+import luan.LuanException;
+
+
+public final class Xml {
+
+	public static String toString(LuanTable tbl) throws LuanException {
+		if( tbl.rawSize() != 1 )
+			throw new LuanException("XML most have 1 root element");
+		Map.Entry entry = tbl.iterator().next();
+		Object key = entry.getKey();
+		if( !(key instanceof String) )
+			throw new LuanException("XML key must be string");
+		String name = (String)key;
+		Object value = entry.getValue();
+		if( !(value instanceof LuanTable) )
+			throw new LuanException("XML root value must be table");
+		LuanTable t = (LuanTable)value;
+		Map<String,String> attributes = attributes(t);
+		XmlElement[] elements = elements(t);
+		XmlElement element = new XmlElement(name,attributes,elements);
+		return element.toString();
+	}
+
+	private static final Integer ONE = new Integer(1);
+
+	private static Map<String,String> attributes(LuanTable tbl) throws LuanException {
+		Object obj = tbl.get(ONE);
+		if( obj==null )
+			return Collections.emptyMap();
+		LuanTable t = (LuanTable)obj;
+		Map<String,String> map = new LinkedHashMap<String,String>();
+		for( Map.Entry entry : t.iterable() ) {
+			String name =(String)entry.getKey();
+			String value =(String)entry.getValue();
+			map.put(name,value);
+		}
+		return map;
+	}
+
+	private static XmlElement[] elements(LuanTable tbl) throws LuanException {
+		List<XmlElement> list = new ArrayList<XmlElement>();
+		for( Map.Entry entry : tbl.iterable() ) {
+			Object key = entry.getKey();
+			if( key.equals(ONE) )
+				continue;
+			String name = (String)key;
+			Object value = entry.getValue();
+			XmlElement element;
+			if( value instanceof String ) {
+				String s = (String)value;
+				element = new XmlElement(name,Collections.emptyMap(),s);
+			} else {
+				LuanTable t = (LuanTable)value;
+				Map<String,String> attributes = attributes(t);
+				XmlElement[] elements = elements(t);
+				element = new XmlElement(name,attributes,elements);
+			}
+			list.add(element);
+		}
+		return list.toArray(new XmlElement[0]);
+	}
+
+
+	public static LuanTable parse(Luan luan,String s) throws ParseException, LuanException {
+		XmlElement element = XmlParser.parse(s);
+		return toTable(luan,new XmlElement[]{element});
+	}
+
+	private static LuanTable toTable(Luan luan,XmlElement[] elements) throws LuanException {
+		LuanTable tbl = new LuanTable(luan);
+		for( XmlElement element : elements ) {
+			if( element.content instanceof String ) {
+				tbl.put(element.name,element.content);
+			} else {
+				XmlElement[] els = (XmlElement[])element.content;
+				LuanTable t = toTable(luan,els);
+				tbl.put(element.name,t);
+			}
+		}
+		return tbl;
+	}
+
+}