Skip to content

Commit

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

Signed-off-by: azerr <azerr@redhat.com>
  • Loading branch information
angelozerr committed Jun 11, 2020
1 parent 427367b commit 6da2665
Show file tree
Hide file tree
Showing 19 changed files with 817 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*******************************************************************************
* 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.List;

import org.eclipse.lemminx.commons.TextDocument;
import org.eclipse.lemminx.dom.DOMAttr;
import org.eclipse.lemminx.services.IXMLFormatter;
import org.eclipse.lemminx.settings.SharedSettings;
import org.eclipse.lemminx.utils.XMLBuilder;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextEdit;
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;

public abstract class AbstractXML2GrammarGenerator<T extends FileContentGeneratorSettings>
implements IFileContentGenerator<Document, T> {

@Override
public TextEdit generate(Document document, SharedSettings sharedSettings, T generatorSettings,
IXMLFormatter formatter) {
String newText = doConvert(document, sharedSettings, generatorSettings);
if (formatter == null) {
return new TextEdit(new Range(new Position(0, 0), new Position(0, 0)), newText);
}
List<? extends TextEdit> edits = formatter.format(new TextDocument(newText, "grammar." + getFileExtension()),
null, sharedSettings);
return edits.isEmpty() ? null : edits.get(0);
}

protected abstract String getFileExtension();

private String doConvert(Document document, SharedSettings sharedSettings, T generatorSettings) {
GrammarInfo grammar = createGrammar(document);
XMLBuilder builder = new XMLBuilder(sharedSettings, "", "");
generate(grammar, builder, generatorSettings);
return builder.toString();
}

protected abstract void generate(GrammarInfo grammar, XMLBuilder builder, T grammarSettings);

private GrammarInfo createGrammar(Document document) {
GrammarInfo grammar = new GrammarInfo();
// 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);
return grammar;
}

private static void fillElements(Node node, ChildrenInfo grammar) {
NodeList children = node.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (child.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) child;
ElementInfo grammarElt = grammar.getElement(element.getLocalName());
// Has text
grammarElt.setHasCharacterContent(hasCharacterContent(element));
// Attributes
NamedNodeMap attributes = element.getAttributes();
if (attributes != null) {
for (int j = 0; j < attributes.getLength(); j++) {
Attr attr = (Attr) attributes.item(i);
grammarElt.getAttribute(attr.getName());
}
}
fillElements(element, grammarElt);
}
}
}

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,25 @@
/*******************************************************************************
* 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;

public class AttributeInfo {

private final String name;

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

public String getName() {
return name;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*******************************************************************************
* 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.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class ChildrenInfo {

private final Map<String, ElementInfo> children;

public ChildrenInfo() {
this.children = new LinkedHashMap<>();
}

public ElementInfo getElement(String name) {
ElementInfo element = children.get(name);
if (element != null) {
return element;
}
element = new ElementInfo(name);
children.put(name, element);
return element;
}

public Collection<ElementInfo> getElements() {
return children.values();
}

public Collection<ElementInfo> getAllElements() {
List<ElementInfo> all = new ArrayList<>();
fillWithAllElements(all);
return all;
}

protected void fillWithAllElements(List<ElementInfo> allElements) {
for (ElementInfo element : getElements()) {
if (!allElements.contains(element)) {
allElements.add(element);
element.fillWithAllElements(allElements);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*******************************************************************************
* 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.HashMap;
import java.util.Map;

public class ElementInfo extends ChildrenInfo {

private final String name;

private final Map<String, AttributeInfo> attributes;

private boolean hasCharacterContent;

public ElementInfo(String name) {
this.name = name;
this.attributes = new HashMap<>();
}

public String getName() {
return name;
}

public AttributeInfo getAttribute(String name) {
AttributeInfo attribute = attributes.get(name);
if (attribute != null) {
return attribute;
}
attribute = new AttributeInfo(name);
attributes.put(name, attribute);
return attribute;
}

public Collection<AttributeInfo> getAttributes() {
return attributes.values();
}

public boolean hasCharacterContent() {
return hasCharacterContent;
}

public void setHasCharacterContent(boolean hasCharacterContent) {
this.hasCharacterContent = hasCharacterContent;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*******************************************************************************
* 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.HashMap;
import java.util.Map;

import org.eclipse.lemminx.extensions.generators.xml2dtd.DTDSettings;
import org.eclipse.lemminx.extensions.generators.xml2dtd.XML2DTDGenerator;
import org.eclipse.lemminx.extensions.generators.xml2xsd.XML2XMLSchemaGenerator;
import org.eclipse.lemminx.extensions.generators.xml2xsd.XMLSchemaSettings;
import org.eclipse.lemminx.services.IXMLFormatter;
import org.eclipse.lemminx.settings.SharedSettings;
import org.eclipse.lsp4j.TextEdit;

/**
* Generator manager which is able to generate :
*
* <ul>
* <li>XML Schema from a given XML.</li>
* <li>DTD from a given XML.</li>
* </ul>
*
*/
public class FileContentGeneratorManager {

private static final FileContentGeneratorManager INSTANCE = new FileContentGeneratorManager();
private final Map<Class<? extends FileContentGeneratorSettings>, IFileContentGenerator<?, ?>> generators;

private FileContentGeneratorManager() {
generators = new HashMap<>();
registerDefaultGenerators();
}

/**
* Register default generators.
*/
private void registerDefaultGenerators() {
registerGenerator(new XML2DTDGenerator(), DTDSettings.class);
registerGenerator(new XML2XMLSchemaGenerator(), XMLSchemaSettings.class);
}

public static FileContentGeneratorManager getInstance() {
return INSTANCE;
}

/**
* Register the given generator by using settings class as key generator.
*
* @param generator the generator.
* @param generatorKey the key of the generator identified by the settings
* class.
*/
public void registerGenerator(IFileContentGenerator<?, ?> generator,
Class<? extends FileContentGeneratorSettings> generatorKey) {
generators.put(generatorKey, generator);
}

/**
* Return the result of the generation from the source XML document and
* settings.
*
* @param <T> the grammar settings.
* @param document
* @param sharedSettings
* @param grammarSettings
* @param formatter
* @return
*/
public <Source, Settings extends FileContentGeneratorSettings> TextEdit generate(Source document,
SharedSettings sharedSettings, Settings grammarSettings, IXMLFormatter formatter) {
@SuppressWarnings("unchecked")
IFileContentGenerator<Source, Settings> generator = (IFileContentGenerator<Source, Settings>) generators
.get(grammarSettings.getClass());
return generator.generate(document, sharedSettings, grammarSettings, formatter);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*******************************************************************************
* 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;

public class FileContentGeneratorSettings {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*******************************************************************************
* 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;

public class GrammarInfo extends ChildrenInfo{

private String defaultNamespace;

public String getDefaultNamespace() {
return defaultNamespace;
}

public void setDefaultNamespace(String defaultNamespace) {
this.defaultNamespace = defaultNamespace;
}

}
Loading

0 comments on commit 6da2665

Please sign in to comment.