Here are two another classes that might interest you.
Benefit:
Provides a way to write data back into XML files.
Example:
- Documentation of status and error messages (in a GUI).
- To modify files that hold test data (might be used by robots that follow up).
The CreateXMLFileTagHandler.java creates a file and inserts a XML element.
The AddElementToXMLFileTagHandler.java adds or overwrites XML elements in a file.
If you think the classes are usefull for you or other users please intergrate it into the official version.
We hope this small contibution will help to spread and develop JFC-Unit.
Thanks a lot.
Tom Wiedenhoeft
P.S. Please note to modify the files XMLConstants.java and TagMapping.properties as shown below.
/**
* This tag handler creates an XML file and inserts
* an XML element with a value.
*
* <h3>Description</h3>
* <pre>
* <createXMLFile
* id="myid"
* file="c:/tmp/my.xml"
* xpath="/AA/BB"
* value="Oktoberfest in Munich"/>
* </pre>
* <p>
* All atributes are mandatory except value.
* </p>
* <p>
* Remember to use an absolute xPath with a leading "/".
* Examples: xpath="/AA/BB", xpath="/AA".
* </p>
* <p>
* References to the file will be accessible by<br>
* - "id" for the java.io.File. Example "bag".<br>
* - "id.name" for the name of the file.
* Example "bag.name" to get "12.xml".<br>
* - "id.path" for the absolut path.
* Example "bag.path" to get "c:/tmp/12.xml".<br>
* - "id.dir" for the directory.
* Example "bag.dir" to get "c:/tmp".<br>
* </p>
* @author tom@smart-tail.com (Tom Wiedenhoeft, DV-Ratio acting under contract with Siemens L&A PA)
*/
public class CreateXMLFileTagHandler extends AbstractTagHandler {
/**
* constructor.
* @param element Element to be processed.
* @param testCase containing test case.
*/
public CreateXMLFileTagHandler(final Element element,
final IXMLTestCase testCase) {
super(element, testCase);
}
/**
* Get the value of the id attribute.
* @return String value of the id attribute.
*/
public String getId() {
return getString(ID);
}
/**
* Get the file attribute from the element.
* @return String The value of the file attribute.
*/
public String getFile() {
return getString(FILE);
}
/**
* Get the xpath of the xmlElement attribute.
* @return String value of the xpath attribute.
*/
public String getXPath() {
return getString(XPATH);
}
/**
* Get the value of the value attribute.
* @return String value of the value attribute.
*/
public String getValue() {
return getString(VALUE);
}
/**
* Process the element.
* Stores an object of type java.io.File for later usage in the cache.
* This object is later accessable via the property "id".
* @throws XMLException may be thrown.
*/
public void processElement() throws XMLException {
validateElement();
// Get the attribute value
String id = this.getId();
// Get the file
File file = this.writeFile();
String name = null;
String path = null;
String dir = null;
if(file != null)
{
name = file.getName();
path = file.getAbsolutePath();
dir = file.getParent().toString();
}
// Store the file
getXMLTestCase().addProperty(id, file);
getXMLTestCase().addProperty(id + "." + NAME, name);
getXMLTestCase().addProperty(id + "." + ABSOLUTE_PATH, path);
getXMLTestCase().addProperty(id + "." + DIRECTORY, dir);
}
/**
* Validate that the element is properly configured.
* @throws XMLException Exception may be thrown if there
* are missing elements.
*/
public void validateElement() throws XMLException {
// check the element tag name
checkElementTagName(CREATE_XML_FILE);
checkRequiredAttribute(ID);
checkRequiredAttribute(FILE);
checkRequiredAttribute(XPATH);
String xpath = this.getXPath();
if("".equals(xpath))
{
throw new XMLException("Attribute 'xpath' is empty.",
null,
getElement(),
getTest().getPropertyCache());
}
}
/**
* Creates a JDOM document according to the xPath and
* writes it to a file.
*
* @return java.io.File where a matching element was found.
* @throws XMLException
*/
private File writeFile() throws XMLException
{
// Get the attribute values
String file = this.getFile();
String xpath = this.getXPath();
String value = this.getValue();
if(value == null)
{
value = "";
}
// Check the directory
File f = new File(file);
File directory = f.getParentFile();
if(! directory.exists())
{
throw new XMLException("Directory does not exist: " + directory,
null,
getElement(),
getTest().getPropertyCache());
}
if(! directory.isDirectory())
{
throw new XMLException(directory + " is not a directory.",
null,
getElement(),
getTest().getPropertyCache());
}
// Check for file with identical name and change the name
// to a file name with a leading digit.
// Example: If "fbag.xml" exists already name it "1fbag.xml".
String fileNameOriginal = f.getName();
int i = 1;
while(f.exists())
{
String fileName = f.getName();
file = directory + File.separator + i++ + fileNameOriginal;
f = new File(file);
}
// Create the document
org.jdom.Document doc = new org.jdom.Document();
org.jdom.Element parentElement = null;
// Look into the attribute xpath.
// Navigate through the xpath (XML element to create).
// Example: xpath = //AA/BB makes <AA><BB></BB></AA>).
StringTokenizer st = new StringTokenizer(xpath, "/");
while(st.hasMoreElements())
{
String elementName = st.nextToken();
if("".equals(elementName))
{
continue;
}
org.jdom.Element e = new org.jdom.Element(elementName);
if(parentElement == null)
{
doc.setRootElement(e);
}
else
{
e.setText(value);
parentElement.addContent(e);
}
parentElement = e;
}
// Write the created document to file
XMLOutputter outp = new XMLOutputter();
outp.setIndent(" ");
outp.setNewlines(true);
BufferedOutputStream out = null;
try {
out = new BufferedOutputStream(new FileOutputStream(file));
outp.output(doc, out);
} catch(IOException e) {
e.printStackTrace();
throw new XMLException(
"Filed to write document for " + file + ". " + e.getMessage(),
null,
getElement(),
getTest().getPropertyCache());
} finally {
if(out != null)
{
try {
out.close();
} catch(IOException e1) {
e1.printStackTrace();
}
}
}
return f;
}
}
/**
* This is tag handler inserts a XML element into an XML file.
* <p>
* <h3>Example 1 - adds an element</h3>
* <p>
* Provided you have a file containing:
* </p>
* <pre>
* <AA><BB>Oktoberfest in Munich</BB></AA>
* </pre>
* <p>
* and want to insert a new element like this
* </p>
* <pre>
* <AA><BB>Oktoberfest in Munich</BB><BB>starts now</BB></AA>
* </pre>
* <p>
* You can use this TagHandler like this
* </p>
* <pre>
* <addElementToXMLFile
* id="myid"
* file="c:/tmp/my.xml"
* xpath="/AA/BB"
* value="starts now"/>
* </pre>
* <p>
* <h3>Example 2 - overwrites an element</h3>
* </p>
* <p>
* Provided you have a file containing:
* </p>
* <pre>
* <AA><BB>Oktoberfest in Munich</BB></AA>
* </pre>
* <p>
* and want to overwrite the element like this
* </p>
* <pre>
* <AA><BB>Beer Festival in Munich</BB></AA>
* </pre>
* <p>
* You can use this TagHandler like this
* </p>
* <pre>
* <addElementToXMLFile
* id="myid"
* file="c:/tmp/my.xml"
* xpath="/AA/BB"
* overwrite="yes"
* value="starts now"/>
* </pre>
* <h3>The attributes</h3>
* <pre>
* id - Referenz for later usage [required]
* file - The file [required]
* xpath - xPath of the XML element [required]
* ignoreErrors - The reading of the file might throw errors.
* The TagHandler can ignore this.
* If ignored the referenz (id) is null and thats it.
* If not ignored (default) an error is thrown.
* (The test will fail.)
* Q: Where can it be usefull?
* A: Parallel working robots. Robots might grab the
* same file and not only read them (move, change,..)
* Q: How can I check if an errors did happen?
* A: Later reference of attribute "id" will be null.
* Default is "no" [optional]
* value - Value inside the XML element
* [required] if "regexp" is not provided
* overwrite - Overwrites an existing element with the same name
* [optional] "yes", "no", "true", "false"
* default is "no"/"false"
* </pre>
* <p>
* TODO: Insert attributes.
* </p>
* <p>
* Remember to use an absolute xPath with a leading "/".
* Examples: xpath="/AA/BB", xpath="/AA".
* </p>
* <p>
* Later references to the file will be accessible by<br>
* - "id" for the java.io.File. Example "myid".<br>
* - "id.name" for the name of the file.
* Example "myid.name" to get "my.xml".<br>
* - "id.path" for the absolut path.
* Example "myid.path" to get "c:/tmp/my.xml".<br>
* - "id.dir" for the directory.
* Example "myid.dir" to get "c:/tmp".<br>
* </p>
* <h3>The behaviour if some elements are added to an existing XML file</h3>
* <p>
* The original file is:
* </p>
* <pre>
* <AA>
* <BB>
* First Text
* </BB>
* </AA>
* </pre>
* <p>
* An element with XPath "/AA/BB" and value "second text" is added and
* results in:
* </p>
* <pre>
* <AA>
* <BB>
* First Text
* </BB>
* <BB>
* second text
* </BB>
* </AA>
* </pre>
* <p>
* Another element with XPath "/AA/BB/CC" and value "third text" is added and
* results in:
* </p>
* <pre>
* <AA>
* <BB>
* First Text
* <CC>
* third Text
* </CC>
* </BB>
* <BB>
* second text
* </BB>
* </AA>
* </pre>
* <p>
* Another element with XPath "/AA/BB/CC" with value "fourth text" is added and
* results in:
* </p>
* <pre>
* <AA>
* <BB>
* First Text
* <CC>
* third Text
* </CC>
* <CC>
* fourth Text
* </CC>
* </BB>
* <BB>
* second text
* </BB>
* </AA>
* </pre>
* @author tom@smart-tail.com (Tom Wiedenhoeft, DV-Ratio acting under contract with Siemens L&A PA)
*/
public class AddElementToXMLFileTagHandler extends AbstractTagHandler {
/**
* constructor.
* @param element Element to be processed.
* @param testCase containing test case.
*/
public AddElementToXMLFileTagHandler(final Element element,
final IXMLTestCase testCase) {
super(element, testCase);
}
/**
* Get the value of the id attribute.
* @return String value of the id attribute.
*/
public String getId() {
return getString(ID);
}
/**
* Get the file attribute from the element.
* @return String The value of the file attribute.
*/
public String getFile() {
return getString(FILE);
}
/**
* Get the xpath of the xmlElement attribute.
* @return String value of the xpath attribute.
*/
public String getXPath() {
return getString(XPATH);
}
/**
* Get the value of the value attribute.
* @return String value of the value attribute.
*/
public String getValue() {
return getString(VALUE);
}
/**
* Get the value of the ignoreErrors attribute.
* @return String value of the ignoreErrors attribute.
*/
public String getIgnoreErrors() {
return getString(IGNORE_ERRORS);
}
/**
* Get the value of the ignoreErrors attribute.
* @return String value of the ignoreErrors attribute.
*/
public String getOverwrite() {
return getString(OVERWRITE);
}
/**
* Process the element.
* Stores an object of type java.io.File for later usage in the cache.
* This object is later accessable via the property "id".
* @throws XMLException may be thrown.
*/
public void processElement() throws XMLException {
validateElement();
// Get the attribute value
String id = this.getId();
// Get the file
File file = this.addElement();
String name = null;
String path = null;
String dir = null;
if(file != null)
{
name = file.getName();
path = file.getAbsolutePath();
dir = file.getParent().toString();
}
// Store the file
getXMLTestCase().addProperty(id, file);
getXMLTestCase().addProperty(id + "." + NAME, name);
getXMLTestCase().addProperty(id + "." + ABSOLUTE_PATH, path);
getXMLTestCase().addProperty(id + "." + DIRECTORY, dir);
}
/**
* Validate that the element is properly configured.
* @throws XMLException Exception may be thrown if there
* are missing elements.
*/
public void validateElement() throws XMLException {
// check the element tag name
checkElementTagName(ADD_ELEMENT_TO_XML_FILE);
checkRequiredAttribute(XPATH);
String xpath = this.getXPath();
if("".equals(xpath))
{
throw new XMLException("Attribute 'xpath' is empty.",
null,
getElement(),
getTest().getPropertyCache());
}
if(! xpath.startsWith("/"))
{
throw new XMLException(
"Invalid value for attribute 'xpath'. \n"
+ "The xPath has to start with a '/' (absolute xPath).",
null,
getElement(),
getTest().getPropertyCache());
}
}
/**
* Overwrites the value of an existing XML element or
* adds an xml-element into a xml-file according to the xPath
* of the element to add or overwrite.
* <p>
* Behavior of this method when some elements are added
* to an existing XML file.
* </p>
* <p>
* The original file is:
* </p>
* <pre>
* <AA>
* <BB>
* First Text
* </BB>
* </AA>
* </pre>
* <p>
* Added element with XPath "/AA/BB" and value "second text"
* results in:
* </p>
* <pre>
* <AA>
* <BB>
* First Text
* </BB>
* <BB>
* second text
* </BB>
* </AA>
* </pre>
* <p>
* Another added Element with XPath "/AA/BB/CC" and value "third text"
* results in:
* </p>
* <pre>
* <AA>
* <BB>
* First Text
* <CC>
* third Text
* </CC>
* </BB>
* <BB>
* second text
* </BB>
* </AA>
* </pre>
* <p>
* Another added Element with XPath "/AA/BB/CC" with value "fourth text"
* results in:
* </p>
* <pre>
* <AA>
* <BB>
* First Text
* <CC>
* third Text
* </CC>
* <CC>
* fourth Text
* </CC>
* </BB>
* <BB>
* second text
* </BB>
* </AA>
* </pre>
*
* @return java.io.File where a matching element was found.
* @throws XMLException
*/
private File addElement() throws XMLException
{
// Get the attribute values
String file = this.getFile();
String xpath = this.getXPath();
String value = this.getValue();
if(value == null)
{
value = "";
}
// In case more than one robot work on the same file.
// Thats the purpose of boolean ignore
String ignoreErrors = this.getIgnoreErrors();
boolean ignore = false;
if(ignoreErrors != null && ! "".equals(ignoreErrors))
{
if(ignoreErrors.trim().equalsIgnoreCase("yes"))
{
ignore = true;;
}
if(ignoreErrors.trim().equalsIgnoreCase("true"))
{
ignore = true;;
}
}
// Check the directory
File f = new File(file);
if(! f.exists())
{
if(ignore)
{
return null;
}
else
{
throw new XMLException("File does not exist: " + f,
null,
getElement(),
getTest().getPropertyCache());
}
}
if(f.isDirectory())
{
throw new XMLException(f + " is a directory and not a file.",
null,
getElement(),
getTest().getPropertyCache());
}
// Create a URL for the file. (Used by SAX Builder)
String fileString = "file:" + file;
URL url = null;
try {
url = new URL(fileString);
} catch(MalformedURLException e) {
throw new XMLException(fileString + " is a malformed URL.",
null,
getElement(),
getTest().getPropertyCache());
}
// Look into the file. Let the SAX builder create an
// XML document.
SAXBuilder builder = new SAXBuilder();
Document doc = null;
try {
doc = builder.build(url);
} catch(JDOMException e) {
if(ignore)
{
System.err.println("The TagHandler will IGNORE the above EXCEPTION");
System.err.println(
"An Exception was thrown inside AddElementToXMLFileTagHandler"
+ "while reading the XML file. Exception message is: "
+ e.getMessage());
return null;
}
else
{
e.printStackTrace();
throw new XMLException(
"Error with file " + this.getFile() + ". " + e.getMessage(),
null,
getElement(),
getTest().getPropertyCache());
}
}
// Insert the new element
// Look into the javadoc for examples
org.jdom.Element root = doc.getRootElement();
String rootName = root.getName();
org.jdom.Element currentElement = null;
// Look into the attribute xpath.
// Navigate through the xpath (XML element to add or overwrite
// Example //AA/BB = <AA><BB></BB></AA>).
// In case the XML element does not exist in the element create
// one.
StringTokenizer st = new StringTokenizer(xpath, "/");
while(st.hasMoreElements())
{
// Go on to next element in xpath
// Example: //AA
// Example: //AA/BB and so on
String elementName = st.nextToken();
if(rootName.equals(elementName))
{
currentElement = root;
}
else
{
if(currentElement == null)
{
// It is not allowed to change the root element
throw new XMLException(
"Not allowed to overwrite the existing root in document \n"
+ this.getFile() + " with " + xpath,
null,
getElement(),
getTest().getPropertyCache());
}
org.jdom.Element tmpElement =
currentElement.getChild(elementName);
// If the element does not exist create it
if(tmpElement == null)
{
tmpElement = new org.jdom.Element(elementName);
currentElement.addContent(tmpElement);
}
else if(! st.hasMoreElements())
{
// an element with same name does already exist
if(! overwrite)
{
// do not overwrite existing element (values)
// create a new element
org.jdom.Element newElement = new org.jdom.Element(elementName);
org.jdom.Element parentElement = tmpElement.getParent();
parentElement.addContent(newElement);
tmpElement = newElement;
}
}
else
{
// follow the tree
}
currentElement = tmpElement;
}
}
// Set the value.
currentElement.setText(value);
// Write the document to file
XMLOutputter outp = new XMLOutputter();
outp.setIndent(" ");
outp.setNewlines(true);
BufferedOutputStream out = null;
try {
out = new BufferedOutputStream(new FileOutputStream(file));
outp.output(doc, out);
} catch(IOException e) {
e.printStackTrace();
throw new XMLException(
"Filed to write document for " + file + ". " + e.getMessage(),
null,
getElement(),
getTest().getPropertyCache());
} finally {
if(out != null)
{
try {
out.close();
} catch(IOException e1) {
e1.printStackTrace();
}
}
}
return f;
}
}
Great Idea. However your implementation is incomplete. Attributes and Comments are not supported via your API.
It would be great if the node to insert were specified as a child node. The parent path would be the XPath specified. Thus any nodes or collections of nodes could be added with a single write operation.
Also, What version of JDom are you using your API does not match that of the 1.0 published version.
Kevin
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Hallo, again!
Here are two another classes that might interest you.
Benefit:
Provides a way to write data back into XML files.
Example:
- Documentation of status and error messages (in a GUI).
- To modify files that hold test data (might be used by robots that follow up).
The CreateXMLFileTagHandler.java creates a file and inserts a XML element.
The AddElementToXMLFileTagHandler.java adds or overwrites XML elements in a file.
If you think the classes are usefull for you or other users please intergrate it into the official version.
We hope this small contibution will help to spread and develop JFC-Unit.
Thanks a lot.
Tom Wiedenhoeft
P.S. Please note to modify the files XMLConstants.java and TagMapping.properties as shown below.
---schnipp -- CreateXMLFileTagHandler.java ---package junit.extensions.xml.elements;
import junit.extensions.xml.IXMLTestCase;
import junit.extensions.xml.XMLException;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import org.jdom.output.XMLOutputter;
import org.jdom.Document;
import org.w3c.dom.Element;
import java.util.StringTokenizer;
/**
* This tag handler creates an XML file and inserts
* an XML element with a value.
*
* <h3>Description</h3>
* <pre>
* <createXMLFile
* id="myid"
* file="c:/tmp/my.xml"
* xpath="/AA/BB"
* value="Oktoberfest in Munich"/>
* </pre>
* <p>
* All atributes are mandatory except value.
* </p>
* <p>
* Remember to use an absolute xPath with a leading "/".
* Examples: xpath="/AA/BB", xpath="/AA".
* </p>
* <p>
* References to the file will be accessible by<br>
* - "id" for the java.io.File. Example "bag".<br>
* - "id.name" for the name of the file.
* Example "bag.name" to get "12.xml".<br>
* - "id.path" for the absolut path.
* Example "bag.path" to get "c:/tmp/12.xml".<br>
* - "id.dir" for the directory.
* Example "bag.dir" to get "c:/tmp".<br>
* </p>
* @author tom@smart-tail.com (Tom Wiedenhoeft, DV-Ratio acting under contract with Siemens L&A PA)
*/
public class CreateXMLFileTagHandler extends AbstractTagHandler {
/**
* constructor.
* @param element Element to be processed.
* @param testCase containing test case.
*/
public CreateXMLFileTagHandler(final Element element,
final IXMLTestCase testCase) {
super(element, testCase);
}
/**
* Get the value of the id attribute.
* @return String value of the id attribute.
*/
public String getId() {
return getString(ID);
}
/**
* Get the file attribute from the element.
* @return String The value of the file attribute.
*/
public String getFile() {
return getString(FILE);
}
/**
* Get the xpath of the xmlElement attribute.
* @return String value of the xpath attribute.
*/
public String getXPath() {
return getString(XPATH);
}
/**
* Get the value of the value attribute.
* @return String value of the value attribute.
*/
public String getValue() {
return getString(VALUE);
}
/**
* Process the element.
* Stores an object of type java.io.File for later usage in the cache.
* This object is later accessable via the property "id".
* @throws XMLException may be thrown.
*/
public void processElement() throws XMLException {
validateElement();
// Get the attribute value
String id = this.getId();
// Get the file
File file = this.writeFile();
String name = null;
String path = null;
String dir = null;
if(file != null)
{
name = file.getName();
path = file.getAbsolutePath();
dir = file.getParent().toString();
}
// Store the file
getXMLTestCase().addProperty(id, file);
getXMLTestCase().addProperty(id + "." + NAME, name);
getXMLTestCase().addProperty(id + "." + ABSOLUTE_PATH, path);
getXMLTestCase().addProperty(id + "." + DIRECTORY, dir);
}
/**
* Validate that the element is properly configured.
* @throws XMLException Exception may be thrown if there
* are missing elements.
*/
public void validateElement() throws XMLException {
// check the element tag name
checkElementTagName(CREATE_XML_FILE);
checkRequiredAttribute(ID);
checkRequiredAttribute(FILE);
checkRequiredAttribute(XPATH);
String xpath = this.getXPath();
if("".equals(xpath))
{
throw new XMLException("Attribute 'xpath' is empty.",
null,
getElement(),
getTest().getPropertyCache());
}
}
/**
* Creates a JDOM document according to the xPath and
* writes it to a file.
*
* @return java.io.File where a matching element was found.
* @throws XMLException
*/
private File writeFile() throws XMLException
{
// Get the attribute values
String file = this.getFile();
String xpath = this.getXPath();
String value = this.getValue();
if(value == null)
{
value = "";
}
// Check the directory
File f = new File(file);
File directory = f.getParentFile();
if(! directory.exists())
{
throw new XMLException("Directory does not exist: " + directory,
null,
getElement(),
getTest().getPropertyCache());
}
if(! directory.isDirectory())
{
throw new XMLException(directory + " is not a directory.",
null,
getElement(),
getTest().getPropertyCache());
}
// Check for file with identical name and change the name
// to a file name with a leading digit.
// Example: If "fbag.xml" exists already name it "1fbag.xml".
String fileNameOriginal = f.getName();
int i = 1;
while(f.exists())
{
String fileName = f.getName();
file = directory + File.separator + i++ + fileNameOriginal;
f = new File(file);
}
// Create the document
org.jdom.Document doc = new org.jdom.Document();
org.jdom.Element parentElement = null;
// Look into the attribute xpath.
// Navigate through the xpath (XML element to create).
// Example: xpath = //AA/BB makes <AA><BB></BB></AA>).
StringTokenizer st = new StringTokenizer(xpath, "/");
while(st.hasMoreElements())
{
String elementName = st.nextToken();
if("".equals(elementName))
{
continue;
}
org.jdom.Element e = new org.jdom.Element(elementName);
if(parentElement == null)
{
doc.setRootElement(e);
}
else
{
e.setText(value);
parentElement.addContent(e);
}
parentElement = e;
}
// Write the created document to file
XMLOutputter outp = new XMLOutputter();
outp.setIndent(" ");
outp.setNewlines(true);
BufferedOutputStream out = null;
try {
out = new BufferedOutputStream(new FileOutputStream(file));
outp.output(doc, out);
} catch(IOException e) {
e.printStackTrace();
throw new XMLException(
"Filed to write document for " + file + ". " + e.getMessage(),
null,
getElement(),
getTest().getPropertyCache());
} finally {
if(out != null)
{
try {
out.close();
} catch(IOException e1) {
e1.printStackTrace();
}
}
}
return f;
}
}
---schnapp -- CreateXMLFileTagHandler.java ---
---schnipp -- AddElementToXMLFileTagHandler.java ---
package junit.extensions.xml.elements;
import junit.extensions.xml.IXMLTestCase;
import junit.extensions.xml.XMLException;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.StringTokenizer;
import org.jdom.input.SAXBuilder;
import org.jdom.output.XMLOutputter;
import org.jdom.Document;
import org.jdom.JDOMException;
import org.w3c.dom.Element;
/**
* This is tag handler inserts a XML element into an XML file.
* <p>
* <h3>Example 1 - adds an element</h3>
* <p>
* Provided you have a file containing:
* </p>
* <pre>
* <AA><BB>Oktoberfest in Munich</BB></AA>
* </pre>
* <p>
* and want to insert a new element like this
* </p>
* <pre>
* <AA><BB>Oktoberfest in Munich</BB><BB>starts now</BB></AA>
* </pre>
* <p>
* You can use this TagHandler like this
* </p>
* <pre>
* <addElementToXMLFile
* id="myid"
* file="c:/tmp/my.xml"
* xpath="/AA/BB"
* value="starts now"/>
* </pre>
* <p>
* <h3>Example 2 - overwrites an element</h3>
* </p>
* <p>
* Provided you have a file containing:
* </p>
* <pre>
* <AA><BB>Oktoberfest in Munich</BB></AA>
* </pre>
* <p>
* and want to overwrite the element like this
* </p>
* <pre>
* <AA><BB>Beer Festival in Munich</BB></AA>
* </pre>
* <p>
* You can use this TagHandler like this
* </p>
* <pre>
* <addElementToXMLFile
* id="myid"
* file="c:/tmp/my.xml"
* xpath="/AA/BB"
* overwrite="yes"
* value="starts now"/>
* </pre>
* <h3>The attributes</h3>
* <pre>
* id - Referenz for later usage [required]
* file - The file [required]
* xpath - xPath of the XML element [required]
* ignoreErrors - The reading of the file might throw errors.
* The TagHandler can ignore this.
* If ignored the referenz (id) is null and thats it.
* If not ignored (default) an error is thrown.
* (The test will fail.)
* Q: Where can it be usefull?
* A: Parallel working robots. Robots might grab the
* same file and not only read them (move, change,..)
* Q: How can I check if an errors did happen?
* A: Later reference of attribute "id" will be null.
* Default is "no" [optional]
* value - Value inside the XML element
* [required] if "regexp" is not provided
* overwrite - Overwrites an existing element with the same name
* [optional] "yes", "no", "true", "false"
* default is "no"/"false"
* </pre>
* <p>
* TODO: Insert attributes.
* </p>
* <p>
* Remember to use an absolute xPath with a leading "/".
* Examples: xpath="/AA/BB", xpath="/AA".
* </p>
* <p>
* Later references to the file will be accessible by<br>
* - "id" for the java.io.File. Example "myid".<br>
* - "id.name" for the name of the file.
* Example "myid.name" to get "my.xml".<br>
* - "id.path" for the absolut path.
* Example "myid.path" to get "c:/tmp/my.xml".<br>
* - "id.dir" for the directory.
* Example "myid.dir" to get "c:/tmp".<br>
* </p>
* <h3>The behaviour if some elements are added to an existing XML file</h3>
* <p>
* The original file is:
* </p>
* <pre>
* <AA>
* <BB>
* First Text
* </BB>
* </AA>
* </pre>
* <p>
* An element with XPath "/AA/BB" and value "second text" is added and
* results in:
* </p>
* <pre>
* <AA>
* <BB>
* First Text
* </BB>
* <BB>
* second text
* </BB>
* </AA>
* </pre>
* <p>
* Another element with XPath "/AA/BB/CC" and value "third text" is added and
* results in:
* </p>
* <pre>
* <AA>
* <BB>
* First Text
* <CC>
* third Text
* </CC>
* </BB>
* <BB>
* second text
* </BB>
* </AA>
* </pre>
* <p>
* Another element with XPath "/AA/BB/CC" with value "fourth text" is added and
* results in:
* </p>
* <pre>
* <AA>
* <BB>
* First Text
* <CC>
* third Text
* </CC>
* <CC>
* fourth Text
* </CC>
* </BB>
* <BB>
* second text
* </BB>
* </AA>
* </pre>
* @author tom@smart-tail.com (Tom Wiedenhoeft, DV-Ratio acting under contract with Siemens L&A PA)
*/
public class AddElementToXMLFileTagHandler extends AbstractTagHandler {
/**
* constructor.
* @param element Element to be processed.
* @param testCase containing test case.
*/
public AddElementToXMLFileTagHandler(final Element element,
final IXMLTestCase testCase) {
super(element, testCase);
}
/**
* Get the value of the id attribute.
* @return String value of the id attribute.
*/
public String getId() {
return getString(ID);
}
/**
* Get the file attribute from the element.
* @return String The value of the file attribute.
*/
public String getFile() {
return getString(FILE);
}
/**
* Get the xpath of the xmlElement attribute.
* @return String value of the xpath attribute.
*/
public String getXPath() {
return getString(XPATH);
}
/**
* Get the value of the value attribute.
* @return String value of the value attribute.
*/
public String getValue() {
return getString(VALUE);
}
/**
* Get the value of the ignoreErrors attribute.
* @return String value of the ignoreErrors attribute.
*/
public String getIgnoreErrors() {
return getString(IGNORE_ERRORS);
}
/**
* Get the value of the ignoreErrors attribute.
* @return String value of the ignoreErrors attribute.
*/
public String getOverwrite() {
return getString(OVERWRITE);
}
/**
* Process the element.
* Stores an object of type java.io.File for later usage in the cache.
* This object is later accessable via the property "id".
* @throws XMLException may be thrown.
*/
public void processElement() throws XMLException {
validateElement();
// Get the attribute value
String id = this.getId();
// Get the file
File file = this.addElement();
String name = null;
String path = null;
String dir = null;
if(file != null)
{
name = file.getName();
path = file.getAbsolutePath();
dir = file.getParent().toString();
}
// Store the file
getXMLTestCase().addProperty(id, file);
getXMLTestCase().addProperty(id + "." + NAME, name);
getXMLTestCase().addProperty(id + "." + ABSOLUTE_PATH, path);
getXMLTestCase().addProperty(id + "." + DIRECTORY, dir);
}
/**
* Validate that the element is properly configured.
* @throws XMLException Exception may be thrown if there
* are missing elements.
*/
public void validateElement() throws XMLException {
// check the element tag name
checkElementTagName(ADD_ELEMENT_TO_XML_FILE);
checkRequiredAttribute(ID);
checkRequiredAttribute(FILE);
checkRequiredAttribute(VALUE);
checkRequiredAttribute(XPATH);
String xpath = this.getXPath();
if("".equals(xpath))
{
throw new XMLException("Attribute 'xpath' is empty.",
null,
getElement(),
getTest().getPropertyCache());
}
if(! xpath.startsWith("/"))
{
throw new XMLException(
"Invalid value for attribute 'xpath'. \n"
+ "The xPath has to start with a '/' (absolute xPath).",
null,
getElement(),
getTest().getPropertyCache());
}
}
/**
* Overwrites the value of an existing XML element or
* adds an xml-element into a xml-file according to the xPath
* of the element to add or overwrite.
* <p>
* Behavior of this method when some elements are added
* to an existing XML file.
* </p>
* <p>
* The original file is:
* </p>
* <pre>
* <AA>
* <BB>
* First Text
* </BB>
* </AA>
* </pre>
* <p>
* Added element with XPath "/AA/BB" and value "second text"
* results in:
* </p>
* <pre>
* <AA>
* <BB>
* First Text
* </BB>
* <BB>
* second text
* </BB>
* </AA>
* </pre>
* <p>
* Another added Element with XPath "/AA/BB/CC" and value "third text"
* results in:
* </p>
* <pre>
* <AA>
* <BB>
* First Text
* <CC>
* third Text
* </CC>
* </BB>
* <BB>
* second text
* </BB>
* </AA>
* </pre>
* <p>
* Another added Element with XPath "/AA/BB/CC" with value "fourth text"
* results in:
* </p>
* <pre>
* <AA>
* <BB>
* First Text
* <CC>
* third Text
* </CC>
* <CC>
* fourth Text
* </CC>
* </BB>
* <BB>
* second text
* </BB>
* </AA>
* </pre>
*
* @return java.io.File where a matching element was found.
* @throws XMLException
*/
private File addElement() throws XMLException
{
// Get the attribute values
String file = this.getFile();
String xpath = this.getXPath();
String value = this.getValue();
if(value == null)
{
value = "";
}
// In case more than one robot work on the same file.
// Thats the purpose of boolean ignore
String ignoreErrors = this.getIgnoreErrors();
boolean ignore = false;
if(ignoreErrors != null && ! "".equals(ignoreErrors))
{
if(ignoreErrors.trim().equalsIgnoreCase("yes"))
{
ignore = true;;
}
if(ignoreErrors.trim().equalsIgnoreCase("true"))
{
ignore = true;;
}
}
// Overwrite or add the XML element?
String overwriteElement = this.getOverwrite();
boolean overwrite = false;
if(overwriteElement != null && ! "".equals(overwriteElement))
{
if(overwriteElement.trim().equalsIgnoreCase("yes"))
{
overwrite = true;;
}
if(overwriteElement.trim().equalsIgnoreCase("true"))
{
overwrite = true;;
}
}
// Check the directory
File f = new File(file);
if(! f.exists())
{
if(ignore)
{
return null;
}
else
{
throw new XMLException("File does not exist: " + f,
null,
getElement(),
getTest().getPropertyCache());
}
}
if(f.isDirectory())
{
throw new XMLException(f + " is a directory and not a file.",
null,
getElement(),
getTest().getPropertyCache());
}
// Create a URL for the file. (Used by SAX Builder)
String fileString = "file:" + file;
URL url = null;
try {
url = new URL(fileString);
} catch(MalformedURLException e) {
throw new XMLException(fileString + " is a malformed URL.",
null,
getElement(),
getTest().getPropertyCache());
}
// Look into the file. Let the SAX builder create an
// XML document.
SAXBuilder builder = new SAXBuilder();
Document doc = null;
try {
doc = builder.build(url);
} catch(JDOMException e) {
if(ignore)
{
System.err.println("The TagHandler will IGNORE the above EXCEPTION");
System.err.println(
"An Exception was thrown inside AddElementToXMLFileTagHandler"
+ "while reading the XML file. Exception message is: "
+ e.getMessage());
return null;
}
else
{
e.printStackTrace();
throw new XMLException(
"Error with file " + this.getFile() + ". " + e.getMessage(),
null,
getElement(),
getTest().getPropertyCache());
}
}
// Insert the new element
// Look into the javadoc for examples
org.jdom.Element root = doc.getRootElement();
String rootName = root.getName();
org.jdom.Element currentElement = null;
// Look into the attribute xpath.
// Navigate through the xpath (XML element to add or overwrite
// Example //AA/BB = <AA><BB></BB></AA>).
// In case the XML element does not exist in the element create
// one.
StringTokenizer st = new StringTokenizer(xpath, "/");
while(st.hasMoreElements())
{
// Go on to next element in xpath
// Example: //AA
// Example: //AA/BB and so on
String elementName = st.nextToken();
if(rootName.equals(elementName))
{
currentElement = root;
}
else
{
if(currentElement == null)
{
// It is not allowed to change the root element
throw new XMLException(
"Not allowed to overwrite the existing root in document \n"
+ this.getFile() + " with " + xpath,
null,
getElement(),
getTest().getPropertyCache());
}
org.jdom.Element tmpElement =
currentElement.getChild(elementName);
// If the element does not exist create it
if(tmpElement == null)
{
tmpElement = new org.jdom.Element(elementName);
currentElement.addContent(tmpElement);
}
else if(! st.hasMoreElements())
{
// an element with same name does already exist
if(! overwrite)
{
// do not overwrite existing element (values)
// create a new element
org.jdom.Element newElement = new org.jdom.Element(elementName);
org.jdom.Element parentElement = tmpElement.getParent();
parentElement.addContent(newElement);
tmpElement = newElement;
}
}
else
{
// follow the tree
}
currentElement = tmpElement;
}
}
// Set the value.
currentElement.setText(value);
// Write the document to file
XMLOutputter outp = new XMLOutputter();
outp.setIndent(" ");
outp.setNewlines(true);
BufferedOutputStream out = null;
try {
out = new BufferedOutputStream(new FileOutputStream(file));
outp.output(doc, out);
} catch(IOException e) {
e.printStackTrace();
throw new XMLException(
"Filed to write document for " + file + ". " + e.getMessage(),
null,
getElement(),
getTest().getPropertyCache());
} finally {
if(out != null)
{
try {
out.close();
} catch(IOException e1) {
e1.printStackTrace();
}
}
}
return f;
}
}
---schnapp -- AddElementToXMLFileTagHandler.java ---
Please add in TagMapping.properties
---schnipp -- TagMapping.properties ---
createXMLFile = junit.extensions.xml.elements.CreateXMLFileTagHandler
addElementToXMLFile = junit.extensions.xml.elements.AddElementToXMLFileTagHandler
---schnapp -- TagMapping.properties ---
Please add in XMLConstants.java
---schnipp -- XMLConstants.java ---
/** xpath */
String XPATH = "xpath";
/** path. */
String ABSOLUTE_PATH = "path";
/** directory. */
String DIRECTORY = "dir";
/** ignoreErrors */
String IGNORE_ERRORS = "ignoreErrors";
/** overwrite */
String OVERWRITE = "overwrite";
---schnapp -- XMLConstants.java ---
Great Idea. However your implementation is incomplete. Attributes and Comments are not supported via your API.
It would be great if the node to insert were specified as a child node. The parent path would be the XPath specified. Thus any nodes or collections of nodes could be added with a single write operation.
Also, What version of JDom are you using your API does not match that of the 1.0 published version.
Kevin