Subversion Repositories general

Compare Revisions

Ignore whitespace Rev 1004 → Rev 1005

/zpath/trunk/src/ak/zpath/DocumentTree.java
0,0 → 1,550
package ak.zpath;
 
import org.w3c.dom.*;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;
import java.io.*;
import java.util.*;
import java.text.ParseException;
 
public class DocumentTree
{
/**
* if true then null values of parameters will be replaced with empty strings
*/
private boolean nullDefaultEmptyString = true;
 
/**
* if true then execution will be terminated if desired node for assigment
* has already a value
*/
private boolean failOnValueSet = true;
 
private PathCreator pathCreator = new PathCreator();
private Document document;
private Element lastElement = null;
 
public DocumentTree(Document document)
{
this.document = document;
}
 
public void updateDocument(String path, Object[] values)
throws ParseException
{
updateElement(path, values);
}
 
public Element updateElement(String path, Object[] values)
throws ParseException
{
return updateElement(null, path, values);
}
 
public Element updateElement(Element start, String path, Object[] values)
throws ParseException
{
return updateElement(start, path, createParamsMap(values));
}
 
public Element updateElement(String path, Map params)
throws ParseException
{
return updateElement(null, path, params);
}
 
public Element updateElement(Element start, String path, Map params)
throws ParseException
{
List elements = pathCreator.createPath(path);
Element target = searchElementInternal(start, elements, params, true);
 
updateTarget(
(PathElement)elements.get(elements.size() - 1), params, target);
 
return target;
}
 
public Element searchElement(String path, Object[] values)
throws ParseException
{
return searchElement(null, path, values);
}
 
public Element searchElement(Element start, String path, Object[] values)
throws ParseException
{
return searchElement(start, path, createParamsMap(values));
}
 
public Element searchElement(String path, Map params)
throws ParseException
{
return searchElement(null, path, params);
}
 
public Element searchElement(Element start, String path, Map params)
throws ParseException
{
return searchElementInternal(start,
pathCreator.createPath(path), params, false);
}
 
protected Element searchElementInternal(Element start, List path, Map params,
boolean update)
throws ParseException
{
Element root = document.getDocumentElement();
PathElement element;
Element target = null;
int startPos = 0;
 
if(path.size() == 0)
throw new PathCreateException("No elements found in path");
 
element = (PathElement)path.get(0);
 
if("/".equals(element.getName())) { // absolute path
element = (PathElement)path.get(1);
 
if(path.size() > 0 && "**".equals(element.getName())) {
// special case: '/**'
startPos = 2;
}
else {
if(root == null) {
if(!update) return null;
 
root = document.createElement(element.getName());
document.appendChild(root);
lastElement = root;
}
else if(!root.getNodeName().equals(element.getName())) {
if(!update)
return null;
else
throw new PathCreateException(
"Document has already root element with name '"
+ root.getNodeName() + "'");
}
 
target = root;
startPos = 2;
}
}
 
if("**".equals(element.getName())) { // ref to last created element
if(lastElement == null)
throw new PathCreateException(
"Cannot go to '**' because no elements was created yet");
 
target = lastElement;
startPos++;
}
else if(startPos == 0) { // it was no slash element at the begin
if(start == null)
throw new PathCreateException(
"No start element specified for relative path");
 
target = start;
startPos = 0;
}
 
target = doStep(path, startPos, params, target, update);
 
return target;
}
 
protected void updateTarget(PathElement element, Map params, Element target)
throws ParseException
{
Node node = null;
String value;
 
// get first value
switch(element.getFirstOperandType()) {
case PathElement.OPERAND_NONE:
return; // nothing to do
 
case PathElement.OPERAND_TEXT:
NodeList list = target.getChildNodes();
boolean done = false;
for(int i = 0; i < list.getLength(); i++) {
Node subnode = list.item(i);
if(subnode.getNodeType() == Node.TEXT_NODE) {
node = subnode;
done = true;
break;
}
}
if(!done) {
node = document.createTextNode(null);
target.appendChild(node);
}
break;
 
case PathElement.OPERAND_ATTRIBUTE:
node = target.getAttributeNode(element.getFirstOperandValue());
if(node == null) {
node = document.createAttribute(element.getFirstOperandValue());
target.setAttributeNode((Attr)node);
}
break;
 
default:
throw new RuntimeException("Unknown first operand type: "
+ element.getFirstOperandType());
}
 
// get second value
switch(element.getSecondOperandType()) {
case PathElement.OPERAND_VARIABLE:
value = (String)params.get(element.getSecondOperandValue());
break;
 
case PathElement.OPERAND_STRING:
value = element.getSecondOperandValue();
break;
 
default:
throw new RuntimeException("Unknown second operand type: "
+ element.getSecondOperandType());
}
 
// set value
String oldValue = node.getNodeValue();
 
switch(element.getOperation()) {
case PathElement.OPERATION_ASSIGN:
// chech if value is already set
if(failOnValueSet && oldValue != null && !oldValue.equals(""))
throw new RuntimeException("Node " + node.getNodeName()
+ " has already value '" + oldValue + "'");
node.setNodeValue(value);
break;
 
case PathElement.OPERATION_APPEND:
if(oldValue == null) oldValue = "";
node.setNodeValue(oldValue + value);
break;
 
default:
throw new RuntimeException("Unknown operation: "
+ element.getOperation());
}
}
 
protected Map createParamsMap(Object[] values)
throws ParseException
{
Map map = new HashMap();
 
if(values != null) {
if(values.length % 2 != 0)
throw new PathCreateException(
"Number of values must match the number of names");
 
for(int i = 0; i < values.length; i += 2) {
if(!(values[i] instanceof String))
throw new PathCreateException("Name of value must be string");
 
if(values[i+1] == null)
;
else if(!(values[i+1] instanceof String))
throw new PathCreateException(
"Value must be string in current version");
 
// save the value
Object value = values[i+1];
 
if(nullDefaultEmptyString) {
if(value == null) value = "";
}
else
throw new PathCreateException(
"Cannot save null value of '" + values[i] + "' to XML");
 
map.put(values[i], value);
}
}
 
return map;
}
 
protected Element doStep(List path, int pos, Map params, Element node,
boolean update)
throws ParseException
{
if(pos >= path.size()) return node;
 
NodeList list = node.getChildNodes();
int count = list.getLength();
PathElement element = (PathElement)path.get(pos);
 
// special element name - parent element
if("..".equals(element.getName())) {
if(node.getParentNode() == null)
throw new PathCreateException(
"Node " + node.getNodeName() + " has no parent");
 
if(!(node.getParentNode() instanceof Element))
throw new PathCreateException(
"Parent of node " + node.getNodeName() + " is not an element");
 
return doStep(path, ++pos, params, (Element)node.getParentNode(), update);
}
 
// ordinal element without the 'new' keyword
if(!element.getNew()) {
for(int i = count - 1; i >= 0; i--) { // go backward
Node subnode = list.item(i);
 
if(subnode.getNodeType() != Node.ELEMENT_NODE)
continue; // go through elements only
 
Element e = (Element)subnode;
 
if(testElement(element, params, e)) {
return doStep(path, ++pos, params, e, update);
}
}
}
 
// nothing found
if(update) { // create a new one
if(element.getName() == null)
throw new PathCreateException("Cannot create element with name '*'");
 
Element newElement = document.createElement(element.getName());
setupElement(element, params, newElement);
node.appendChild(newElement);
lastElement = newElement;
 
return doStep(path, ++pos, params, newElement, update);
}
else { // not allowed to change document
return null;
}
}
 
protected void setupElement(PathElement element, Map params, Element node)
{
for(Iterator i = element.getConditions().iterator(); i.hasNext(); ) {
PathCondition condition = (PathCondition)i.next();
Node subnode = null;
String value;
 
// get first value
switch(condition.getFirstOperandType()) {
case PathElement.OPERAND_TEXT:
subnode = document.createTextNode("");
node.appendChild(subnode);
break;
 
case PathElement.OPERAND_ATTRIBUTE:
subnode = document.createAttribute(condition.getFirstOperandValue());
node.setAttributeNode((Attr)subnode);
break;
 
default:
throw new RuntimeException("Unknown first operand type: "
+ condition.getFirstOperandType());
}
 
// get second value
switch(condition.getSecondOperandType()) {
case PathElement.OPERAND_VARIABLE:
value = (String)params.get(condition.getSecondOperandValue());
break;
 
case PathElement.OPERAND_STRING:
value = condition.getSecondOperandValue();
break;
 
default:
throw new RuntimeException("Unknown second operand type: "
+ condition.getSecondOperandType());
}
 
// set
switch(condition.getOperation()) {
case PathElement.OPERATION_EQUAL:
subnode.setNodeValue(value);
break;
 
default:
throw new RuntimeException("Unknown operation: "
+ condition.getOperation());
}
}
}
 
protected boolean testElement(PathElement element, Map params, Element node)
{
// element name is specified and does not match
if(!element.getName().equals("*")
&& !node.getNodeName().equals(element.getName()))
{
return false;
}
 
for(Iterator i = element.getConditions().iterator(); i.hasNext(); ) {
PathCondition condition = (PathCondition)i.next();
String firstValue = null;
String secondValue;
 
// get first value
switch(condition.getFirstOperandType()) {
case PathElement.OPERAND_TEXT:
NodeList list = node.getChildNodes();
for(int j = 0; j < list.getLength(); j++) {
Node subnode = list.item(j);
if(subnode.getNodeType() == Node.TEXT_NODE) {
firstValue = subnode.getNodeValue();
break;
}
}
break;
 
case PathElement.OPERAND_ATTRIBUTE:
firstValue = node.getAttribute(condition.getFirstOperandValue());
break;
 
default:
throw new RuntimeException("Unknown first operand type: "
+ condition.getFirstOperandType());
}
 
// get second value
switch(condition.getSecondOperandType()) {
case PathElement.OPERAND_VARIABLE:
secondValue = (String)params.get(condition.getSecondOperandValue());
break;
 
case PathElement.OPERAND_STRING:
secondValue = condition.getSecondOperandValue();
break;
 
default:
throw new RuntimeException("Unknown second operand type: "
+ condition.getSecondOperandType());
}
 
// compare
switch(condition.getOperation()) {
case PathElement.OPERATION_EQUAL:
if(!firstValue.equals(secondValue)) return false;
break;
 
default:
throw new RuntimeException("Unknown operation: "
+ condition.getOperation());
}
}
 
return true;
}
 
public static void main(String[] args)
throws Exception
{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.newDocument();
DocumentTree tree = new DocumentTree(document);
Transformer transformer
= TransformerFactory.newInstance().newTransformer();
 
// test update -------------------------------------------------------------
 
tree.updateDocument(
"/Vertrag/bbb@name:='BBB'",
null );
 
tree.updateDocument(
"/Vertrag/bbb@id:='ZZZ'",
null );
 
Element ccc = tree.updateElement(
"/Vertrag/bbb[@name='BBB' and @id=${attr1}]/ccc@test := $value",
new Object[] {
"value", null,
"attr1", "ZZZ"
} );
 
tree.updateElement(
"/Vertrag/bbb[@name='BBB' and @id=${attr1}]/ccc@test2 := $value",
new Object[] {
"value", "66666",
"attr1", "ZZZ"
} );
 
tree.updateElement(ccc,
"subccc# := 'test relative path'",
(Object[])null);
 
tree.updateElement(ccc,
"../../subnode@attr := 'test relative path 2'",
(Object[])null);
 
tree.updateDocument(
"/Vertrag/bbb/ddd# .= '123'",
null);
 
tree.updateDocument(
"/Vertrag/bbb/ddd# .= '456'",
null);
 
tree.updateDocument(
"/Vertrag/bbb/ccc[new]@test3 := $value",
new Object[] {
"value", "ABCD",
"attr1", "ZZZ"
} );
 
tree.updateDocument(
"/Vertrag/bbb/*# .= '!!!'",
null);
 
tree.updateDocument(
"/**@nnn := 'test last element'",
null);
 
tree.updateDocument(
"/**/../..@lll := 'test last element'",
null);
 
tree.updateDocument(
"/Vertrag/Sparten/Sparte[@bezeichnung=${a}]"
+ "/SpartenspezifischerTeil/Wagnis[@nummer=${c}]/Objekt[@nummer=${d}]",
new Object[] {
"a", "Gebaeude",
"c", "0001",
"d", "0002"
} );
 
tree.updateDocument(
"/Vertrag/Sparten/Sparte[@bezeichnung=${a}]"
+ "/SpartenspezifischerTeil/Wagnis[@nummer=${c}]/Objekt[@nummer=${d}]",
new Object[] {
"a", "Gebaeude",
"c", "0001",
"d", "0003"
} );
 
// test search -------------------------------------------------------------
 
tree.searchElement("/Vertrag/Sparten", (Object[])null);
tree.searchElement("/Vertrag2/Sparten", (Object[])null);
tree.searchElement("/Vertrag/nnnn/mmmm/mmm", (Object[])null);
 
// write result ------------------------------------------------------------
 
transformer.transform(new DOMSource(document), new StreamResult(
new FileOutputStream("result.xml")));
 
(new XmlSaver(new FileOutputStream("result2.xml"))).save(document);
}
}