Mercurial Hosting > luan
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; + } + +}