Created 2 modules: parsley-grammar and parsley-examples
This commit is contained in:
		
							parent
							
								
									6be7f76372
								
							
						
					
					
						commit
						c9c1454f90
					
				
					 29 changed files with 278032 additions and 57 deletions
				
			
		|  | @ -0,0 +1,38 @@ | |||
| package com.albertoventurini.parsley.examples.graphml; | ||||
| 
 | ||||
| import java.util.Map; | ||||
| 
 | ||||
| public final class Edge { | ||||
| 
 | ||||
|     private final String id; | ||||
|     private final String source; | ||||
|     private final String target; | ||||
|     private final Map<String, String> data; | ||||
| 
 | ||||
|     public Edge( | ||||
|             final String id, | ||||
|             final String source, | ||||
|             final String target, | ||||
|             final Map<String, String> data) { | ||||
|         this.id = id; | ||||
|         this.source = source; | ||||
|         this.target = target; | ||||
|         this.data = data; | ||||
|     } | ||||
| 
 | ||||
|     public String id() { | ||||
|         return id; | ||||
|     } | ||||
| 
 | ||||
|     public String source() { | ||||
|         return source; | ||||
|     } | ||||
| 
 | ||||
|     public String target() { | ||||
|         return target; | ||||
|     } | ||||
| 
 | ||||
|     public Map<String, String> data() { | ||||
|         return data; | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,150 @@ | |||
| package com.albertoventurini.parsley.examples.graphml; | ||||
| 
 | ||||
| import com.albertoventurini.parsley.grammar.Grammar; | ||||
| import com.albertoventurini.parsley.grammar.ParseTree; | ||||
| import com.albertoventurini.parsley.grammar.rules.Rule; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.nio.file.Files; | ||||
| import java.nio.file.Path; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.function.BiFunction; | ||||
| import java.util.function.Function; | ||||
| import java.util.stream.Collectors; | ||||
| 
 | ||||
| import static com.albertoventurini.parsley.grammar.Grammar.*; | ||||
| 
 | ||||
| public class GrammarGraphMLParser implements GraphMLParser { | ||||
| 
 | ||||
|     private final Grammar grammar; | ||||
| 
 | ||||
|     public GrammarGraphMLParser() { | ||||
| 
 | ||||
|         final Rule comment = sequence(string("<!--"), until("-->")).as("comment"); | ||||
|         final Rule comments = zeroOrMore(comment).as("comments"); | ||||
| 
 | ||||
|         final Rule xmlHeader = string("<?xml version='1.0' ?>").as("header"); | ||||
| 
 | ||||
|         final Rule attribute = sequence( | ||||
|                 takeWhile(c -> c != '=' && c != '>'), | ||||
|                 character('='), | ||||
|                 character('\''), | ||||
|                 takeWhile(c -> c != '\''), | ||||
|                 character('\'')).as("attribute"); | ||||
| 
 | ||||
|         final Function<String, Rule> tagFunc = (s) -> sequence( | ||||
|                 character('<'), | ||||
|                 string(s).as("tagName"), | ||||
|                 zeroOrMore(attribute).as("attributes"), | ||||
|                 character('>')).as("tag:" + s); | ||||
| 
 | ||||
|         final Function<String, Rule> closingTagFunc = (s) -> sequence( | ||||
|                 string("</"), | ||||
|                 string(s), | ||||
|                 character('>')).as("closingTag:" + s); | ||||
| 
 | ||||
|         final BiFunction<String, Rule, Rule> elementFunc = (tagName, content) -> sequence( | ||||
|                 tagFunc.apply(tagName).as("tag:" + tagName), | ||||
|                 content.as("content"), | ||||
|                 closingTagFunc.apply(tagName).as("closingTag:" + tagName)); | ||||
| 
 | ||||
|         final Rule data = elementFunc.apply("data", takeWhile(c -> c != '<')).as("dataElem"); | ||||
| 
 | ||||
|         final Rule node = elementFunc.apply("node", zeroOrMore(data)).as("nodeElem"); | ||||
| 
 | ||||
|         final Rule edge = elementFunc.apply("edge", zeroOrMore(data)).as("edgeElem"); | ||||
| 
 | ||||
|         final Rule graph = sequence( | ||||
|                 tagFunc.apply("graph"), | ||||
|                 oneOrMore(node).as("nodes"), | ||||
|                 oneOrMore(edge).as("edges"), | ||||
|                 closingTagFunc.apply("graph")).as("graphElem"); | ||||
| 
 | ||||
|         final Rule key = sequence(tagFunc.apply("key"), closingTagFunc.apply("key")) | ||||
|                 .as("keyElem"); | ||||
| 
 | ||||
|         final Rule graphml = sequence( | ||||
|                 tagFunc.apply("graphml"), | ||||
|                 oneOrMore(key).as("keys"), | ||||
|                 graph, | ||||
|                 closingTagFunc.apply("graphml")).as("graphmlElem"); | ||||
| 
 | ||||
|         final Rule graphmlFile = sequence( | ||||
|                 xmlHeader, | ||||
|                 comments.discard(), | ||||
|                 graphml).as("graphmlFile"); | ||||
| 
 | ||||
|         grammar = new Grammar(graphmlFile, null); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public GraphMLParseResult parse(final String filePath) { | ||||
|         try { | ||||
|              return grammar | ||||
|                      .parse(Files.readString(Path.of(filePath))) | ||||
|                      .map(this::processParseTree) | ||||
|                      .orElseThrow(() -> new RuntimeException("Unable to parse graphml file: " + filePath)); | ||||
|         } catch (IOException e) { | ||||
|             throw new RuntimeException(e); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private GraphMLParseResult processParseTree(final ParseTree parseTree) { | ||||
| 
 | ||||
|         final List<Node> nodes = parseTree.getFirstDescendantByName("nodes").map(n -> | ||||
|             n.getChildren() | ||||
|                     .stream() | ||||
|                     .map(this::processNodeElem) | ||||
|                     .collect(Collectors.toList()) | ||||
|         ).orElseThrow(); | ||||
| 
 | ||||
|         final List<Edge> edges = parseTree.getFirstDescendantByName("edges").map(n -> | ||||
|                 n.getChildren() | ||||
|                         .stream() | ||||
|                         .map(this::processEdgeElem) | ||||
|                         .collect(Collectors.toList()) | ||||
|         ).orElseThrow(); | ||||
| 
 | ||||
|         return new GraphMLParseResult(nodes, edges); | ||||
|     } | ||||
| 
 | ||||
|     private Node processNodeElem(final ParseTree nodeElem) { | ||||
|         final String id = extractAttribute(nodeElem.child("tag:node"), "id"); | ||||
| 
 | ||||
|         final Map<String, String> data = nodeElem.child("content").getChildren() | ||||
|                 .stream() | ||||
|                 .map(this::extractDataElemContent) | ||||
|                 .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); | ||||
| 
 | ||||
|         return new Node(id, data); | ||||
|     } | ||||
| 
 | ||||
|     private Edge processEdgeElem(final ParseTree edgeElem) { | ||||
|         final String id = extractAttribute(edgeElem.child("tag:edge"), "id"); | ||||
|         final String source = extractAttribute(edgeElem.child("tag:edge"), "source"); | ||||
|         final String target = extractAttribute(edgeElem.child("tag:edge"), "target"); | ||||
| 
 | ||||
|         final Map<String, String> data = edgeElem.child("content").getChildren() | ||||
|                 .stream() | ||||
|                 .map(this::extractDataElemContent) | ||||
|                 .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); | ||||
| 
 | ||||
|         return new Edge(id, source, target, data); | ||||
|     } | ||||
| 
 | ||||
|     private Map.Entry<String, String> extractDataElemContent(final ParseTree dataElem) { | ||||
|         final var attribute = extractAttribute(dataElem.child("tag:data"), "key"); | ||||
|         final var content = dataElem.child(1).getText(); | ||||
|         return Map.entry(attribute, content); | ||||
|     } | ||||
| 
 | ||||
|     private String extractAttribute(final ParseTree tag, final String attributeKey) { | ||||
|         return tag.child("attributes").getChildren() | ||||
|                 .stream() | ||||
|                 .filter(a -> a.child(0).getText().equals(attributeKey)) | ||||
|                 .map(a -> a.child(1).getText()) | ||||
|                 .findFirst() | ||||
|                 .orElseThrow(); | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,22 @@ | |||
| package com.albertoventurini.parsley.examples.graphml; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| public final class GraphMLParseResult { | ||||
| 
 | ||||
|     private final List<Node> nodes; | ||||
|     private final List<Edge> edges; | ||||
| 
 | ||||
|     public GraphMLParseResult(final List<Node> nodes, final List<Edge> edges) { | ||||
|         this.nodes = nodes; | ||||
|         this.edges = edges; | ||||
|     } | ||||
| 
 | ||||
|     public List<Node> nodes() { | ||||
|         return nodes; | ||||
|     } | ||||
| 
 | ||||
|     public List<Edge> edges() { | ||||
|         return edges; | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,5 @@ | |||
| package com.albertoventurini.parsley.examples.graphml; | ||||
| 
 | ||||
| public interface GraphMLParser { | ||||
|     GraphMLParseResult parse(String filePath); | ||||
| } | ||||
|  | @ -0,0 +1,22 @@ | |||
| package com.albertoventurini.parsley.examples.graphml; | ||||
| 
 | ||||
| import java.util.Map; | ||||
| 
 | ||||
| public final class Node { | ||||
| 
 | ||||
|     private final String id; | ||||
|     private final Map<String, String> data; | ||||
| 
 | ||||
|     public Node(final String id, final Map<String, String> data) { | ||||
|         this.id = id; | ||||
|         this.data = data; | ||||
|     } | ||||
| 
 | ||||
|     public String id() { | ||||
|         return id; | ||||
|     } | ||||
| 
 | ||||
|     public Map<String, String> data() { | ||||
|         return data; | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,90 @@ | |||
| package com.albertoventurini.parsley.examples.graphml; | ||||
| 
 | ||||
| import com.albertoventurini.parsley.grammar.Grammar; | ||||
| import com.albertoventurini.parsley.grammar.rules.Rule; | ||||
| import org.junit.jupiter.api.Test; | ||||
| 
 | ||||
| import java.nio.file.Files; | ||||
| import java.nio.file.Path; | ||||
| import java.util.function.BiFunction; | ||||
| import java.util.function.Function; | ||||
| 
 | ||||
| import static com.albertoventurini.parsley.grammar.Grammar.*; | ||||
| import static org.junit.jupiter.api.Assertions.assertTrue; | ||||
| 
 | ||||
| public class GraphMLGrammarTest { | ||||
| 
 | ||||
|     private final Grammar grammar; | ||||
| 
 | ||||
|     public GraphMLGrammarTest() { | ||||
|         final Rule comment = sequence(string("<!--"), until("-->")); | ||||
|         final Rule comments = zeroOrMore(comment); | ||||
| 
 | ||||
|         final Rule xmlHeader = string("<?xml version='1.0' ?>").as("header"); | ||||
| 
 | ||||
|         final Rule attribute = sequence( | ||||
|                 takeWhile(c -> c != '=' && c != '>'), | ||||
|                 character('='), | ||||
|                 character('\''), | ||||
|                 takeWhile(c -> c != '\''), | ||||
|                 character('\'')).as("attribute"); | ||||
| 
 | ||||
|         final Function<String, Rule> tagFunc = (s) -> sequence( | ||||
|                 character('<'), | ||||
|                 string(s), | ||||
|                 zeroOrMore(attribute).as("attributes"), | ||||
|                 character('>')); | ||||
| 
 | ||||
|         final Function<String, Rule> closingTagFunc = (s) -> sequence( | ||||
|                 string("</"), | ||||
|                 string(s), | ||||
|                 character('>')); | ||||
| 
 | ||||
|         final BiFunction<String, Rule, Rule> elementFunc = (tagName, content) -> sequence( | ||||
|                 tagFunc.apply(tagName).as("tag:" + tagName), | ||||
|                 content.as("content"), | ||||
|                 closingTagFunc.apply(tagName).as("closingTag:" + tagName)); | ||||
| 
 | ||||
|         final Rule data = elementFunc.apply("data", takeWhile(c -> c != '<')).as("dataElem"); | ||||
| 
 | ||||
|         final Rule node = elementFunc.apply("node", zeroOrMore(data)).as("nodeElem"); | ||||
| 
 | ||||
|         final Rule edge = elementFunc.apply("edge", zeroOrMore(data)).as("edgeElem"); | ||||
| 
 | ||||
|         final Rule graph = sequence( | ||||
|                 tagFunc.apply("graph"), | ||||
|                 oneOrMore(node).as("nodes"), | ||||
|                 oneOrMore(edge).as("edges"), | ||||
|                 closingTagFunc.apply("graph")).as("graphElem"); | ||||
| 
 | ||||
|         final Rule key = sequence( | ||||
|                 tagFunc.apply("key"), | ||||
|                 closingTagFunc.apply("key")).as("keyElem"); | ||||
| 
 | ||||
|         final Rule graphml = sequence( | ||||
|                 tagFunc.apply("graphml"), | ||||
|                 oneOrMore(key).as("keys"), | ||||
|                 graph, | ||||
|                 closingTagFunc.apply("graphml")).as("graphmlElem"); | ||||
| 
 | ||||
|         final Rule graphmlFile = sequence(xmlHeader, graphml).as("graphmlFile"); | ||||
| 
 | ||||
|         grammar = new Grammar(graphmlFile, comments); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void graphmlGrammar_shouldParseGraphmlFile() throws Exception { | ||||
|         final var result = | ||||
|                 grammar.parse(Files.readString(Path.of("src/test/resources/graphml/graphA.graphml"))); | ||||
| 
 | ||||
|         assertTrue(result.isPresent()); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void graphmlGrammar_shouldParseLargeGraphmlFile() throws Exception { | ||||
|         final var result = | ||||
|                 grammar.parse(Files.readString(Path.of("src/test/resources/graphml/air-routes-latest.graphml"))); | ||||
| 
 | ||||
|         assertTrue(result.isPresent()); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										277256
									
								
								parsley-examples/src/test/resources/graphml/air-routes-latest.graphml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										277256
									
								
								parsley-examples/src/test/resources/graphml/air-routes-latest.graphml
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										74
									
								
								parsley-examples/src/test/resources/graphml/graphA.graphml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								parsley-examples/src/test/resources/graphml/graphA.graphml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,74 @@ | |||
| <?xml version='1.0' ?> | ||||
| <!-- ******************************************************************************* --> | ||||
| <!-- Sample file containing selected air routes between selected airports.           --> | ||||
| <graphml xmlns='http://graphml.graphdrawing.org/xmlns'> | ||||
| <key id='type'    for='node' attr.name='type'    attr.type='string'></key> | ||||
|   <key id='code'    for='node' attr.name='code'    attr.type='string'></key> | ||||
|   <key id='icao'    for='node' attr.name='icao'    attr.type='string'></key> | ||||
|   <key id='desc'    for='node' attr.name='desc'    attr.type='string'></key> | ||||
|   <key id='region'  for='node' attr.name='region'  attr.type='string'></key> | ||||
|   <key id='runways' for='node' attr.name='runways' attr.type='int'></key> | ||||
|   <key id='longest' for='node' attr.name='longest' attr.type='int'></key> | ||||
|   <key id='elev'    for='node' attr.name='elev'    attr.type='int'></key> | ||||
|   <key id='country' for='node' attr.name='country' attr.type='string'></key> | ||||
|   <key id='city'    for='node' attr.name='city'    attr.type='string'></key> | ||||
|   <key id='lat'     for='node' attr.name='lat'     attr.type='double'></key> | ||||
|   <key id='lon'     for='node' attr.name='lon'     attr.type='double'></key> | ||||
|   <key id='author'  for='node' attr.name='author'  attr.type='string'></key> | ||||
|   <key id='date'    for='node' attr.name='date'    attr.type='string'></key> | ||||
|   <key id='dist'    for='edge' attr.name='dist'    attr.type='int'></key> | ||||
|   <key id='labelV'  for='node' attr.name='labelV'  attr.type='string'></key> | ||||
|   <key id='labelE'  for='edge' attr.name='labelE'  attr.type='string'></key> | ||||
| 
 | ||||
|   <graph id='routes' edgedefault='directed'> | ||||
|     <node id='0'> | ||||
|       <data key='labelV'>version</data> | ||||
|       <data key='type'>version</data> | ||||
|       <data key='code'>0.84</data> | ||||
|       <data key='date'>2020-01-30 02:17:18 UTC</data> | ||||
|       <data key='author'>Kelvin R. Lawrence</data> | ||||
|       <data key='desc'>Air Routes Data - Version: 0.84 Generated: 2020-01-30 02:17:18 UTC; Graph created by Kelvin R. Lawrence; Please let me know of any errors you find in the graph.</data> | ||||
|     </node> | ||||
|       <node id='1'> | ||||
|         <data key='labelV'>airport</data> | ||||
|         <data key='type'>airport</data> | ||||
|         <data key='code'>ATL</data> | ||||
|         <data key='icao'>KATL</data> | ||||
|         <data key='city'>Atlanta</data> | ||||
|         <data key='desc'>Hartsfield - Jackson Atlanta International Airport</data> | ||||
|         <data key='region'>US-GA</data> | ||||
|         <data key='runways'>5</data> | ||||
|         <data key='longest'>12390</data> | ||||
|         <data key='elev'>1026</data> | ||||
|         <data key='country'>US</data> | ||||
|         <data key='lat'>33.6366996765137</data> | ||||
|         <data key='lon'>-84.4281005859375</data> | ||||
|       </node> | ||||
|       <node id='2'> | ||||
|         <data key='labelV'>airport</data> | ||||
|         <data key='type'>airport</data> | ||||
|         <data key='code'>ANC</data> | ||||
|         <data key='icao'>PANC</data> | ||||
|         <data key='city'>Anchorage</data> | ||||
|         <data key='desc'>Anchorage Ted Stevens</data> | ||||
|         <data key='region'>US-AK</data> | ||||
|         <data key='runways'>3</data> | ||||
|         <data key='longest'>12400</data> | ||||
|         <data key='elev'>151</data> | ||||
|         <data key='country'>US</data> | ||||
|         <data key='lat'>61.1744003295898</data> | ||||
|         <data key='lon'>-149.996002197266</data> | ||||
|       </node> | ||||
| 
 | ||||
| 
 | ||||
|   <edge id='3742' source='1' target='3'> | ||||
|     <data key='labelE'>route</data> | ||||
|     <data key='dist'>811</data> | ||||
|   </edge> | ||||
|   <edge id='3743' source='1' target='4'> | ||||
|     <data key='labelE'>route</data> | ||||
|     <data key='dist'>214</data> | ||||
|   </edge> | ||||
| 
 | ||||
|   </graph> | ||||
| </graphml> | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Alberto Venturini
						Alberto Venturini