Skip to content

Commit

Permalink
Grammar generator from XML
Browse files Browse the repository at this point in the history
Fixes #778

Signed-off-by: azerr <azerr@redhat.com>
  • Loading branch information
angelozerr committed Jun 16, 2020
1 parent 427367b commit 39ef689
Show file tree
Hide file tree
Showing 21 changed files with 1,321 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/*******************************************************************************
* Copyright (c) 2020 Red Hat Inc. and others.
* All rights reserved. This program and the accompanying materials
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.extensions.generators;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.lemminx.commons.TextDocument;
import org.eclipse.lemminx.dom.DOMAttr;
import org.eclipse.lemminx.services.IXMLFullFormatter;
import org.eclipse.lemminx.settings.SharedSettings;
import org.eclipse.lemminx.utils.StringUtils;
import org.eclipse.lemminx.utils.XMLBuilder;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
* Abstract class to generate a grammar (XSD, DTD, etcc) from a given XML
* source.
*
* <p>
* The process is to build a generic {@link Grammar} instance from the XML
* source and each implementation uses this grammar information to generate the
* proper grammar.
* </p>
*
* @param <T> the grammar generator settings.
*/
public abstract class AbstractXML2GrammarGenerator<T extends FileContentGeneratorSettings>
implements IFileContentGenerator<Document, T> {

@Override
public String generate(Document document, SharedSettings sharedSettings, T generatorSettings,
IXMLFullFormatter formatter) {
String newText = doGenerate(document, sharedSettings, generatorSettings);
if (formatter == null) {
return newText;
}
return formatter.formatFull(new TextDocument(newText, "grammar." + getFileExtension()), sharedSettings);
}

protected abstract String getFileExtension();

private String doGenerate(Document document, SharedSettings sharedSettings, T generatorSettings) {
// Create the generic grammar information from the XML source document.
Grammar grammar = createGrammar(document, isFlat());
XMLBuilder builder = new XMLBuilder(sharedSettings, "", "");
// Generate the grammar content from the grammar information.
generate(grammar, generatorSettings, builder);
return builder.toString();
}

protected boolean isFlat() {
return false;
}

/**
* Generate the grammar content from the given grammr information into the given
* builder.
*
* @param grammar the grammar information.
* @param grammarSettings the grammar settings
* @param out the XML builder to update.
*/
protected abstract void generate(Grammar grammar, T grammarSettings, XMLBuilder out);

private Grammar createGrammar(Document document, boolean flat) {
Grammar grammar = new Grammar();
// Update default namespace
String defaultNamespace = null;
Element documentElement = document.getDocumentElement();
if (documentElement != null) {
defaultNamespace = document.getDocumentElement().getAttribute(DOMAttr.XMLNS_ATTR);
}
grammar.setDefaultNamespace(defaultNamespace);
// Update elements information
fillElements(document, grammar, grammar, flat);
return grammar;
}

private static void fillElements(Node node, Grammar grammar, ContainerDeclaration parent, boolean flat) {
if (!node.hasChildNodes()) {
return;
}
NodeList children = node.getChildNodes();

// Parent
if (parent instanceof ElementDeclaration) {
List<String> tags = new ArrayList<>();
ElementDeclaration parentDecl = (ElementDeclaration) parent;
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (child.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) child;
String localName = element.getLocalName();
if (!StringUtils.isEmpty(localName)) {
tags.add(localName);
}
}
}
parentDecl.addChildHierarchy(tags);

// ((ElementDeclaration) container).addTagName(element.getTagName());
}

for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (child.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) child;
ElementDeclaration elementDecl = getElementDecl(grammar, parent, flat, element);
// Update count occurrences
elementDecl.incrementOccurrences();
// Update has text
if (!elementDecl.hasCharacterContent()) {
elementDecl.setHasCharacterContent(hasCharacterContent(element));
}
// Update attributes
NamedNodeMap attributes = element.getAttributes();
if (attributes != null) {
for (int j = 0; j < attributes.getLength(); j++) {
Attr attr = (Attr) attributes.item(i);
elementDecl.getAttribute(attr.getName());
}
}
fillElements(element, grammar, elementDecl, flat);
}
}
}

private static ElementDeclaration getElementDecl(Grammar grammar, ContainerDeclaration container, boolean flat,
Element element) {
String name = element.getLocalName();
if (flat) {
ElementDeclaration elementDecl = grammar.getElement(name);
container.addElement(elementDecl);
return elementDecl;
}
return container.getElement(element.getLocalName());
}

private static boolean hasCharacterContent(Element element) {
NodeList children = element.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (child.getNodeType() == Node.TEXT_NODE) {
return true;
}
}
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*******************************************************************************
* Copyright (c) 2020 Red Hat Inc. and others.
* All rights reserved. This program and the accompanying materials
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.extensions.generators;

/**
* Attribute declaration.
*
*/
public class AttributeDeclaration {

private final String name;

public AttributeDeclaration(String name) {
this.name = name;
}

/**
* Returns the attribute name.
*
* @return the attribute name.
*/
public String getName() {
return name;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package org.eclipse.lemminx.extensions.generators;

import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

// TODO : cleanup this class!!!
public class ChildrenProperties {

private List<String> firstSequence;

public static class Cardinality {

private long min;

private long max;

public void setMin(long min) {
this.min = min;
}

public void setMax(long max) {
this.max = max;
}

public long getMin() {
return min;
}

public long getMax() {
return max;
}
}

private boolean sequenced;

private Map<String, Cardinality> cardinalities;

public ChildrenProperties() {
this.cardinalities = new LinkedHashMap<>();
this.sequenced = true;
}

public void addChildHierarchy(List<String> tags) {
boolean first = firstSequence == null;
Map<String, Long> tagsCount = tags.stream().collect( //
Collectors.groupingBy( //
Function.identity(), LinkedHashMap::new, Collectors.counting() //
));
List<String> filteredTags = new LinkedList<>(tagsCount.keySet());
if (firstSequence == null) {
firstSequence = filteredTags;
}
for (Map.Entry<String, Cardinality> entry : cardinalities.entrySet()) {
String tag = entry.getKey();
Cardinality cardinality = entry.getValue();
Long count = tagsCount.remove(tag);
if (count != null) {
cardinality.setMin(Math.min(count, cardinality.getMin()));
cardinality.setMax(Math.max(count, cardinality.getMax()));
} else {
cardinality.setMin(0);
}
}

for (Map.Entry<String, Long> entry : tagsCount.entrySet()) {
String tag = entry.getKey();
Cardinality cardinality = cardinalities.get(tag);
if (cardinality == null) {
cardinality = new Cardinality();
cardinalities.put(tag, cardinality);
}
Long count = tagsCount.get(tag);
if (first) {
cardinality.setMin(count);
cardinality.setMax(count);
} else {
cardinality.setMin(Math.min(count, cardinality.getMin()));
cardinality.setMax(Math.max(count, cardinality.getMax()));
}
}

for (Map.Entry<String, Cardinality> entry : cardinalities.entrySet()) {
String tag = entry.getKey();
Cardinality cardinality = entry.getValue();
if (cardinality.getMin() == 0) {
firstSequence.remove(tag);
filteredTags.remove(tag);
}
}

if (sequenced) {
sequenced = filteredTags.equals(firstSequence);
}
}

public Map<String, Cardinality> getCardinalities() {
return cardinalities;
}

public boolean isSequenced() {
return sequenced;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.eclipse.lemminx.extensions.generators;

import java.util.Arrays;
import java.util.Map.Entry;

import org.eclipse.lemminx.extensions.generators.ChildrenProperties.Cardinality;

public class ChildrenPropertiesTest {

public static void main(String[] args) {
ChildrenProperties properties = new ChildrenProperties();
properties.addChildHierarchy(Arrays.asList("a", "c"));
//properties.addChildHierarchy(Arrays.asList("c", "a"));
properties.addChildHierarchy(Arrays.asList("a", "c", "c"));
properties.addChildHierarchy(Arrays.asList("a", "b", "c"));
System.err.println("Sequenced:" + properties.isSequenced());
for (Entry<String, Cardinality> entry : properties.getCardinalities().entrySet()) {
System.err
.println(entry.getKey() + " [" + entry.getValue().getMin() + "-" + entry.getValue().getMax() + "]");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*******************************************************************************
* Copyright (c) 2020 Red Hat Inc. and others.
* All rights reserved. This program and the accompanying materials
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.extensions.generators;

import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;

/**
* Children information.
*
*/
public class ContainerDeclaration {

private final Map<String, ElementDeclaration> container;

public ContainerDeclaration() {
this.container = new LinkedHashMap<>();
}

/**
* Returns the element information for the given name and create it if not
* found.
*
* @param name the element name
* @return the element information for the given name and create it if not
* found.
*/
public ElementDeclaration getElement(String name) {
ElementDeclaration element = container.get(name);
if (element != null) {
return element;
}
element = new ElementDeclaration(name);
addElement(element);
return element;
}

public void addElement(ElementDeclaration element) {
container.put(element.getName(), element);
}

/**
* Returns the elements information of the node.
*
* @return the elements information of the node.
*/
public Collection<ElementDeclaration> getElements() {
return container.values();
}

}
Loading

0 comments on commit 39ef689

Please sign in to comment.