Commit 3dbbe35b authored by Dr. Martin Goik's avatar Dr. Martin Goik

Improed xml to sql solution

parent 32b4e910
.project
.classpath
/.settings/
/target/
products.sql
A1.log
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="catalog">
<xs:complexType>
<xs:sequence>
<xs:element ref="product" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="product">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="description" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="age" type="xs:int" minOccurs="0" maxOccurs="1"/>
</xs:sequence>
<xs:attribute name="id" type="xs:ID" use="required"/>
</xs:complexType>
</xs:element>
</xs:schema>
DROP TABLE IF EXISTS Description;
DROP TABLE IF EXISTS Product;
CREATE TABLE Product (
id CHAR(20) NOT NULL PRIMARY KEY
,name VARCHAR(255) NOT NULL
......@@ -6,18 +9,19 @@ CREATE TABLE Product (
CREATE TABLE Description (
product CHAR(20) NOT NULL REFERENCES Product
,orderIndex int NOT NULL -- preserving the order of descriptions belonging to a given product
,text VARCHAR(255) NOT NULL
,UNIQUE(product, orderIndex)
);
-- example data corresponding to products.xml --
INSERT INTO Product VALUES ('mpt', 'Monkey Picked Tea', NULL);
INSERT INTO Description VALUES('mpt', 'Picked only by specially trained monkeys');
INSERT INTO Description VALUES('mpt', 'Rare wild Chinese tea');
INSERT INTO Product VALUES ('instantTent', '4-Person Instant Tent', 15);
INSERT INTO Description VALUES('instantTent', 'Exclusive WeatherTec system.');
INSERT INTO Description VALUES('instantTent', '4-person, 1-room tent');
INSERT INTO Description VALUES('instantTent', 'Pre-attached tent poles');
-- tidy up --
DROP TABLE Description;
DROP TABLE Product;
\ No newline at end of file
-- Product lacking age property --
INSERT INTO Product (id, name) VALUES ('mpt', 'Monkey Picked Tea');
INSERT INTO Description VALUES('mpt', 0, 'Picked only by specially trained monkeys');
INSERT INTO Description VALUES('mpt', 1, 'Rare wild Chinese tea');
INSERT INTO Product VALUES ('instantTent', '4-Person Instant Tent', 15);
INSERT INTO Description VALUES('instantTent', 0, 'Exclusive WeatherTec system.');
INSERT INTO Description VALUES('instantTent', 1, '4-person, 1-room tent');
INSERT INTO Description VALUES('instantTent', 2, 'Pre-attached tent poles');
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.hdm-stuttgart.mi.sda1</groupId>
<artifactId>catalog2sql</artifactId>
<version>0.8</version>
<packaging>jar</packaging>
<name>catalog2sql</name>
<!--Fixme: Add a sensible project related domain here -->
<url>http://somedomain.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>de.hdm-stuttgart.mi.sda1</groupId>
<artifactId>saxerrorhandler</artifactId>
<version>0.8</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.10.1</version>
<configuration/>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.3</version>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>de.hdm_stuttgart.mi.sda1.catalog2sax.App</Main-Class>
</manifestEntries>
</transformer>
</transformers>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE catalog [
<!ELEMENT catalog (product*)>
<!ELEMENT product (name, description*, age?)>
<!ATTLIST product id ID #REQUIRED>
<!ELEMENT name (#PCDATA)>
<!ELEMENT description (#PCDATA)>
<!ELEMENT age (#PCDATA)>
]>
<catalog>
<catalog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="Schema/catalog.xsd">
<product id="mpt">
<name>Monkey Picked Tea</name>
<description>Rare wild Chinese tea</description>
......
package de.hdm_stuttgart.mi.sda1.catalog2sql;
import java.io.IOException;
import java.io.PrintStream;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import de.hdm_stuttgart.mi.sda1.catalog2sql.handler.CatalogContentHandler;
import de.hdm_stuttgart.mi.sda1.saxerrorhandler.handler.SaxErrorHandler;
/**
* A simple SAX parser demo
*
*/
public class App {
private static final Logger log = LogManager.getLogger(App.class);
/**
* @param args Unused
* @throws SAXException
* @throws ParserConfigurationException
* @throws IOException
*/
public static void main( String[] args ) throws ParserConfigurationException, SAXException, IOException {
log.info("Creating SAX parser");
final SAXParserFactory saxPf = SAXParserFactory.newInstance();
final SAXParser saxParser = saxPf.newSAXParser();
final XMLReader xmlReader = saxParser.getXMLReader();
final PrintStream sqlOutputStream = new PrintStream("products.sql");
final SqlFormatter sqlFormatter = new SqlFormatter(sqlOutputStream);
log.info("Registering content- and error handler instances");
xmlReader.setContentHandler(new CatalogContentHandler(sqlFormatter));
xmlReader.setErrorHandler(new SaxErrorHandler(System.err));
final String xmlDocumentInstanceFilename = "products.xml";
log.info("Start parsing file '" + xmlDocumentInstanceFilename + "'");
xmlReader.parse(xmlDocumentInstanceFilename);
sqlOutputStream.close();
}
}
package de.hdm_stuttgart.mi.sda1.catalog2sql;
import java.io.PrintStream;
import java.util.List;
/**
* Formatting product related INSERT statements
*
*/
public class SqlFormatter {
final PrintStream output;
/**
* Writing SQL statements to standard output
*/
public SqlFormatter() {
this.output = System.out;
}
/**
* @param output SQL output destination stream
*/
public SqlFormatter(final PrintStream output) {
this.output = output;
}
/**
* Create an SQL INSERT statement corresponding to a &lt;product id='...' &gt; entry
* lacking age specification.
* @param productId See {@link #insertproduct(String, String, int)}
* @param productName See {@link #insertproduct(String, String, int)}
*/
public void insertproduct(final String productId, final String productName) {
output.println("INSERT INTO Product (id, name) VALUES ('" + productId + "', '" + productName + "');");
}
/**
* Create an SQL INSERT statement corresponding to a &lt;product id='...' &gt; entry
* @param productId The product's unique id property
* @param productName The product's end user readable name.
* @param age The product's age
*/
public void insertproduct(final String productId, final String productName, final int age) {
output.println("INSERT INTO Product VALUES ('" + productId + "', '" + productName + "', " + age + ");");
}
/**
* Adding a description to a given product.
*
* @param productId The description belongs to this product
* @param descriptions All descriptions belonging to productId
*/
public void insertDescription(final String productId, final List<String> descriptions) {
for (int i = 0; i < descriptions.size(); i++) {
output.println("INSERT INTO Description VALUES('" + productId + "', " + i + ", '" + descriptions.get(i) + "');");
}
output.println("-- end of current product entry --\n");
}
}
package de.hdm_stuttgart.mi.sda1.catalog2sql.handler;
import java.util.List;
import java.util.Vector;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import de.hdm_stuttgart.mi.sda1.catalog2sql.SqlFormatter;
/**
* Turning XML data into SQL INSERT statements
*
*/
public class CatalogContentHandler implements ContentHandler {
private static final Logger log = LogManager.getLogger(CatalogContentHandler.class);
Locator locator = null;
final SqlFormatter sqlFormatter;
private final List<String> currentDescriptions = new Vector<String>();
final StringBuffer currentElementContent = new StringBuffer();
String productId, productName, productAgeString;
/**
* @param sqlFormatter SQL INSERT statement formatter
*/
public CatalogContentHandler(final SqlFormatter sqlFormatter) {
this.sqlFormatter = sqlFormatter;
}
@Override
public void setDocumentLocator(Locator locator) {
this.locator = locator;
}
// Convenience Method
String displayWithLocatio(final String saxMsg) {
if (null == locator) {
return saxMsg;
} else {
return "File position (" + locator.getLineNumber() + ", " + locator.getColumnNumber() + "): " + saxMsg;
}
}
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
currentElementContent.setLength(0);
switch(qName) {
case "product":
productAgeString = null; // Value will be altered if &lt;age&gt; is present
productId = attributes.getValue("id");
log.info(displayWithLocatio("Product id=" + productId));
break;
}
}
@Override
public void endElement(final String uri, final String localName, final String qName)
throws SAXException {
switch(qName) {
case "product":
if (null == productAgeString) {
sqlFormatter.insertproduct(productId, productName);
} else {
try {
sqlFormatter.insertproduct(productId, productName, Integer.parseInt(productAgeString));
} catch (NumberFormatException ex) {
log.error("Property <age> is not of integer value:" + productAgeString);
}
}
flushDescriptionEntries();
break;
case "name":
productName = currentElementContent.toString();
break;
case "description":
// Do not interfere with the current INSERT INTO Product ...
// statement. Instead postpone related INSERT INTO Description ...
// operations, see flushDescriptionEntries().
currentDescriptions.add(currentElementContent.toString());
break;
case "age":
productAgeString = currentElementContent.toString();
break;
}
}
private void flushDescriptionEntries() {
// Add <description> related INSERTs
sqlFormatter.insertDescription(productId, currentDescriptions);
// Next <product> may be yet to come, so
// clear the current set of descriptions.
currentDescriptions.clear();
}
@Override
public void characters(final char[] ch, final int start, final int length)
throws SAXException {
currentElementContent.append(new String(ch, start,length));
}
// We don't need these remaining callbacks
@Override public void startDocument() throws SAXException {}
@Override public void endDocument() throws SAXException {}
@Override public void startPrefixMapping(String prefix, String uri)
throws SAXException {}
@Override public void endPrefixMapping(String prefix)
throws SAXException {}
@Override public void ignorableWhitespace(char[] ch, int start, int length)
throws SAXException {}
@Override public void processingInstruction(String target, String data)
throws SAXException {}
@Override public void skippedEntity(String name) throws SAXException {}
}
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<File name="A1" fileName="A1.log" append="false">
<PatternLayout pattern="%t %-5p %c{2} - %m%n"/>
</File>
<Console name="STDOUT" target="SYSTEM_OUT">
<PatternLayout pattern="%C{2} (%F:%L) - %m%n"/>
</Console>
</Appenders>
<Loggers>
<!-- You my want to define class or package level per-logger rules -->
<Logger name="de.hdm_stuttgart.mi.sda1.catalog2sax.App" level="debug">
<AppenderRef ref="A1"/>
</Logger>
<Root level="debug">
<AppenderRef ref="STDOUT"/>
</Root>
</Loggers>
</Configuration>
\ No newline at end of file
package de.hdm_stuttgart.mi.sda1.catalog2sax;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.apache.commons.io.FileUtils;
import org.junit.Assert;
import org.junit.Test;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import de.hdm_stuttgart.mi.sda1.catalog2sql.SqlFormatter;
import de.hdm_stuttgart.mi.sda1.catalog2sql.handler.CatalogContentHandler;
import de.hdm_stuttgart.mi.sda1.saxerrorhandler.handler.SaxErrorHandler;
/**
* Unit test comparing the converter's output to a given reference file.
*/
public class AppTest {
/**
* Dummy test method
* @throws SAXException
* @throws IOException
* @throws ParserConfigurationException
*/
@Test
public void testApp() throws IOException, SAXException, ParserConfigurationException {
final SAXParserFactory saxPf = SAXParserFactory.newInstance();
final SAXParser saxParser = saxPf.newSAXParser();
final XMLReader xmlReader = saxParser.getXMLReader();
final String sqlGeneratedFilename = "products.sql";
final PrintStream sqlOutputStream = new PrintStream(sqlGeneratedFilename);
final SqlFormatter sqlFormatter = new SqlFormatter(sqlOutputStream);
xmlReader.setContentHandler(new CatalogContentHandler(sqlFormatter));
xmlReader.setErrorHandler(new SaxErrorHandler(System.err));
final String xmlDocumentInstanceFilename = "products.xml";
xmlReader.parse(xmlDocumentInstanceFilename);
sqlOutputStream.close();
final String referenceFilename = "src/test/products.reference.sql";
Assert.assertTrue( "Comparing '" + referenceFilename + "' and '" + sqlGeneratedFilename + "'",
FileUtils.contentEquals(new File(referenceFilename), new File(sqlGeneratedFilename)));
}
}
INSERT INTO Product (id, name) VALUES ('mpt', 'Monkey Picked Tea');
INSERT INTO Description VALUES('mpt', 0, 'Rare wild Chinese tea');
INSERT INTO Description VALUES('mpt', 1, 'Picked only by specially trained monkeys');
-- end of current product entry --
INSERT INTO Product VALUES ('instantTent', '4-Person Instant Tent', 15);
INSERT INTO Description VALUES('instantTent', 0, '4-person, 1-room tent');
INSERT INTO Description VALUES('instantTent', 1, 'Pre-attached tent poles');
INSERT INTO Description VALUES('instantTent', 2, 'Exclusive WeatherTec system.');
-- end of current product entry --
/target/
/.settings/
.classpath
.project
A1.log
dependency-reduced-pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.hdm-stuttgart.mi.sda1</groupId>
<artifactId>saxerrorhandler</artifactId>
<version>0.8</version>
<packaging>jar</packaging>
<name>saxerrorhandler</name>
<url>http://www.mi.hdm-stuttgart.de/freedocs</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.10.1</version>
<configuration/>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.3</version>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>de.hdm_stuttgart.mi.sda1.saxerrorhandler.App</Main-Class>
</manifestEntries>
</transformer>
</transformers>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
package de.hdm_stuttgart.mi.sda1.saxerrorhandler.handler;
import java.io.PrintStream;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
/**
* A SAX error handler providing line and column number information with respect
* to files being parsed.
*
* Output will be written to a stream
*
*/
public class SaxErrorHandler implements ErrorHandler {
private static final Logger log = LogManager.getLogger(SaxErrorHandler.class);
final PrintStream problemOutputStream;
/**
* Parsing problems will only published to internal logger.
*/
public SaxErrorHandler() {
problemOutputStream = null;
}
/**
* Parsing problems will be published to internal logger and
* problemOutputStream.
*
* @param problemOutputStream
*/
public SaxErrorHandler(final PrintStream problemOutputStream) {
this.problemOutputStream = problemOutputStream;
}
public void warning(SAXParseException e) {
final String msg = "[Warning]" + getLocationString(e);
log.warn(msg);
if (null != problemOutputStream) {
problemOutputStream.print(msg);
}
}
public void error(SAXParseException e) {
final String msg = "[Error]" + getLocationString(e);
log.error(msg);
if (null != problemOutputStream) {
problemOutputStream.print(msg);
}
}
public void fatalError(SAXParseException e) throws SAXException {
final String msg = "[Fatal Error]" + getLocationString(e);
log.fatal(msg);
if (null != problemOutputStream) {
problemOutputStream.print(msg);
}
}
private String getLocationString(SAXParseException e) {
return " line " + e.getLineNumber() + ", column " + e.getColumnNumber()
+ ":" + e.getMessage();