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); |
} |
} |