Java XML DOM error when adding elements - java

I am trying to replicate this XML:
<?xml version="1.0"?>
<AccessRequest xml:lang="en-US">
<AccessLicenseNumber>YourLicenseNumber</AccessLicenseNumber>
<UserId>YourUserID</UserId>
<Password>YourPassword</Password>
</AccessRequest>
<?xml version="1.0"?>
<AddressValidationRequest xml:lang="en-US">
<Request>
<TransactionReference>
<CustomerContext>Your Test Case Summary Description</CustomerContext>
<XpciVersion>1.0</XpciVersion>
</TransactionReference>
<RequestAction>XAV</RequestAction>
<RequestOption>3</RequestOption>
</Request>
<AddressKeyFormat>
<AddressLine>AIRWAY ROAD SUITE 7</AddressLine>
<PoliticalDivision2>SAN DIEGO</PoliticalDivision2>
<PoliticalDivision1>CA</PoliticalDivision1>
<PostcodePrimaryLow>92154</PostcodePrimaryLow>
<CountryCode>US</CountryCode>
</AddressKeyFormat>
</AddressValidationRequest>
I am using one class to build the request:
public UpsRequestBuilder()
{
try
{
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
doc = docBuilder.newDocument();
}
catch(Exception e)
{
System.out.println(e.getMessage());
}
}
public void accessRequestBuilder(String accessKey, String username, String password)
{
Element accessRequest = doc.createElement("AccessRequest");
doc.appendChild(accessRequest);
Element license = doc.createElement("AccessLicenseNumber");
accessRequest.appendChild(license);
license.setTextContent(accessKey);
Element userId = doc.createElement("UserId");
accessRequest.appendChild(userId);
userId.setTextContent(username);
Element pass = doc.createElement("Password");
accessRequest.appendChild(pass);
pass.setTextContent(password);
System.out.println("completed Requestbuilder");
}
public void addAddress(Address address)
{
Element addressKeyFormat = doc.createElement("AddressKeyFormat");
doc.appendChild(addressKeyFormat);
Element addressLine = doc.createElement("AddressLine");
addressKeyFormat.appendChild(addressLine);
addressLine.setTextContent(address.getState() + ' ' + address.getStreet2());
Element city = doc.createElement("PoliticalDivision2");
addressKeyFormat.appendChild(city);
city.setTextContent(address.getCity());
Element state = doc.createElement("PoliticalDivision1");
addressKeyFormat.appendChild(state);
state.setTextContent(address.getState());
Element zip = doc.createElement("PostcodePrimaryLow");
addressKeyFormat.appendChild(zip);
zip.setTextContent(address.getZip());
Element country = doc.createElement("CountryCode");
addressKeyFormat.appendChild(country);
country.setTextContent(address.getCountry());
System.out.println("completed addAddress");
}
public void validateAddressRequest(String customerContextString, String action)
{
Element addressValidation = doc.createElement("AddressValidationRequest");
doc.appendChild(addressValidation);
Element transactionReference = doc.createElement("TransactionReference");
addressValidation.appendChild(transactionReference);
Element customerContext = doc.createElement("CustomerContext");
Element version = doc.createElement("XpciVersion");
transactionReference.appendChild(customerContext);
customerContext.setTextContent(customerContextString); //TODO figure out a way to optionally pass context text
transactionReference.appendChild(version);
version.setTextContent("1.0");//change this if the api version changes
Element requestAction = doc.createElement("RequestAction");
addressValidation.appendChild(requestAction);
requestAction.setTextContent(action);
System.out.println("completed validateAddressRequest");
}
And this is the function that uses it:
public void validateAddress(Address address)
{
UpsRequestBuilder request = new UpsRequestBuilder();
request.accessRequestBuilder(accessKey, username, password);
request.validateAddressRequest("", "3");
request.addAddress(address);
System.out.println(request.toString());
}
When I try and print out the XML from this, I get the error "HIERARCHY_REQUEST_ERR: An attempt was made to insert a node where it is not permitted." It happens in the validateAddressRequest function when I try and add the addressValidation element to the document (doc). Here is the exact line:
doc.appendChild(addressValidation);
what is the problem with adding this element to the document?

what is the problem with adding this element to the document?
You're trying to add it at the top level of the document. You can't do that, as the document already has a root element. Any XML document can only have a single root element.
The XML you've shown at the top of your question isn't a single XML document - it's two.

Related

remove element by position

I have an xml which has a simple set of data.
This data is displayed in a simple table and each row of data is assigned an ID in the table based on the position in the xml ( <xsl:value-of select="position()"
/> ). I cant add an id attribute to the data because its not my data, but I need to locate elements based on this position and remove them.
public class Delete extends HttpServlet {
private final String XML_FILE = "data.xml";
public void init() throws ServletException {
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// Disable browser caching
response.setHeader("Cache-Control", "private, no-store, no-cache, must-revalidate");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0);
String index = request.getParameter("delete");
try {
// Load the current data.xml
SAXBuilder builder = new SAXBuilder();
Document xml_document = builder.build(new File(getServletContext().getRealPath("/") + XML_FILE));
Element root = xml_document.getRootElement();
root.removeChild(index);
XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat());
outputter.output(xml_document, new FileWriter(getServletContext().getRealPath("/") + XML_FILE));
}
catch(Exception ex) {}
// Once we have processed the input we were given
// redirect the web browser to the main page.
response.sendRedirect("/");
}
public void destroy() {
}
}
This code does not remove the correct data. Anyone know how to find the child of the root element by its position?
#rolfl
int index = Integer.parseInt(delete);
Element root = xml_document.getRootElement();
root.getChildren().remove(index);
This does not remove any elements.
Your problem here is that the process is getting the index to remove as a string, and that's then calling the removeChild(String) method .... which looks for the first child that has an element tag name of whatever (string) value is in the index.
What you want to do, instead, is to convert the index to an int, and then treat the children of the root as a List.... something like:
int index = Integer.parseInt(request.getParameter("delete"));
root.getChildren().remove(index);
See the documentation for getChildren().
This is how I got it to work. Not sure if its a great solution but it works.
SAXBuilder builder = new SAXBuilder();
Document xml_document = builder.build(new File(getServletContext().getRealPath("/") + XML_FILE));
// Get root element
Element root = xml_document.getRootElement();
// Create a list of the children of the root element
List<Element> kids = root.getChildren();
// Interate through list of elements and delete (detach) the element at position index.
int i = 1;
for (Element element : kids)
{
if(i == index)
{
element.detach();
break;
}
else
{
i = i + 1;
}
}
I got the root element with
Element root = xml_document.getRootElement();
Made a list of it's children elements with
List<Element> kids = root.getChildren();
Then iterated through this list until I reached the index of the element to delete then did .detach on this element
int i = 1;
for (Element element : kids)
{
if(i == index)
{
element.detach();
break;
}
else
{
i = i + 1;
}
}
If anyone can update this to show an easier way to remove the element please do so. It feels like there must be an easier way to detach an element without the iteration. Anyway, as I said it works.

Unable to parse element attribute with XOM

I'm attempting to parse an RSS field using the XOM Java library. Each entry's image URL is stored as an attribute for the <img> element, as seen below.
<rss version="2.0">
<channel>
<item>
<title>Decision Paralysis</title>
<link>https://xkcd.com/1801/</link>
<description>
<img src="https://imgs.xkcd.com/comics/decision_paralysis.png"/>
</description>
<pubDate>Mon, 20 Feb 2017 05:00:00 -0000</pubDate>
<guid>https://xkcd.com/1801/</guid>
</item>
</channel>
</rss>
Attempting to parse <img src=""> with .getFirstChildElement("img") only returns a null pointer, making my code crash when I try to retrieve <img src= ...>. Why is my program failing to read in the <img> element, and how can I read it in properly?
import nu.xom.*;
public class RSSParser {
public static void main() {
try {
Builder parser = new Builder();
Document doc = parser.build ( "https://xkcd.com/rss.xml" );
Element rootElement = doc.getRootElement();
Element channelElement = rootElement.getFirstChildElement("channel");
Elements itemList = channelElement.getChildElements("item");
// Iterate through itemList
for (int i = 0; i < itemList.size(); i++) {
Element item = itemList.get(i);
Element descElement = item.getFirstChildElement("description");
Element imgElement = descElement.getFirstChildElement("img");
// Crashes with NullPointerException
String imgSrc = imgElement.getAttributeValue("src");
}
}
catch (Exception error) {
error.printStackTrace();
System.exit(1);
}
}
}
There is no img element in the item. Try
if (imgElement != null) {
String imgSrc = imgElement.getAttributeValue("src");
}
What the item contains is this:
<description><img
src="http://imgs.xkcd.com/comics/us_state_names.png"
title="Technically DC isn't a state, but no one is too
pedantic about it because they don't want to disturb the snakes
."
alt="Technically DC isn't a state, but no one is too pedantic about it because they don't want to disturb the snakes." />
</description>
That's not an img elment. It's plain text.
I managed to come up with a somewhat hacky solution using regex and pattern matching.
// Iterate through itemList
for (int i = 0; i < itemList.size(); i++) {
Element item = itemList.get(i);
String descString = item.getFirstChildElement("description").getValue();
// Parse image URL (hacky)
String imgSrc = "";
Pattern pattern = Pattern.compile("src=\"[^\"]*\"");
Matcher matcher = pattern.matcher(descString);
if (matcher.find()) {
imgSrc = descString.substring( matcher.start()+5, matcher.end()-1 );
}
}

Java - XML Parsing using XPATH

I have XML:
<Table>
<Row ss:Index="74" ss:AutoFitHeight="0" ss:Height="14">
<Cell ss:Index="1" ss:MergeAcross="3" ss:StyleID="s29">
<ss:Data ss:Type="Number" xmlns="http://www.w3.org/TR/REC-html40">
0.00
</ss:Data>
</Cell>
<Cell ss:Index="15" ss:MergeAcross="5" ss:StyleID="s29">
<ss:Data ss:Type="Number" xmlns="http://www.w3.org/TR/REC-html40">
4.57
</ss:Data>
</Cell>
</Row>
Here is code used to extract the content, eg. "0.00", based on row index & cell index:
public static String getCellValueNum(String filename, int rowIdx, int colIdx) {
// search for Table element anywhere in the source
String tableElementPattern = "//*[name()='Table']";
// search for Row element with given number
String rowPattern = String.format("/*[name()='Row' and #ss:Index='%d']", rowIdx) ;
// search for Cell element with given column number
String cellPattern = String.format("/*[name()='Cell' and #ss:Index='%d']", colIdx) ;
// search for element that has ss:Type="String" attribute, search for element with text under it and get text name
String cellStringContent = "/*[#ss:Type='Number']/*[text()]/text()";
String completePattern = tableElementPattern + rowPattern + cellPattern + cellStringContent;
try (FileReader reader = new FileReader(filename)) {
XPath xPath = getXpathProcessor();
Node n = (Node)xPath.compile(completePattern)
.evaluate(new InputSource(reader), XPathConstants.NODE);
if (n.getNodeType() == Node.TEXT_NODE) {
return n.getNodeValue().trim();
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static XPath getXpathProcessor() {
// this is where the custom implementation of NamespaceContext is used
NamespaceContext context = new NamespaceContextMap(
"html", "http://www.w3.org/TR/REC-html40",
//"xsl", "http://www.w3.org/1999/XSL/Transform",
"o", "urn:schemas-microsoft-com:office:office",
"x", "urn:schemas-microsoft-com:office:excel",
"ss", "urn:schemas-microsoft-com:office:spreadsheet");
XPath xpath = XPathFactory.newInstance().newXPath();
xpath.setNamespaceContext(context);
return xpath;
}
It works perfectly fine when 'ss:Type='String'', But when ss:Type='Number' It gives error:
java.lang.NullPointerException
at XpathBill.getCellValueNum(XpathBill.java:55)
at XpathBill.main(XpathBill.java:100)
I think here:
if (n.getNodeType() == Node.TEXT_NODE)
It should be something else instead of TEXT_NODE, I tried other NodeType Named Constants, but it didnt work.
Please Help.
Thank you!

.getTextContent returns text from child elements too

I'm trying to get XML parsing down (and yes I know there's easier ways to parse/validate like xstream) but I can't seem to get text content of just a single element. For example:
<container>
<element0>textThatIWant</element0> //only returned by .getTextContent
<element1>
<subelement0>textThatIDontWant</subelement0> //but also returned by
<subelement1>textThatIDontWant</subelement1> //.getTextContent
</element1>
<container>
I'm piping results out to the console and get mostly what I'm looking for but the only way I seem to get the text strings is with .getTextContent() which returns all text in the sub elements, as well, without whitespace (or else I'd have split on spaces) or .getNodeValue().toString() which throws nullPointerExceptions. #Jihar mentioned something like .getTextValue() but Eclipse doesn't recognize it (maybe there's something I can implement/inherit/whatever to add capability), any help?
Here's the code I'm using:
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import java.io.*;
public class Test {
public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
StringBuilder xmlStringBuilder = new StringBuilder();
String appendage = "..." //This string holds the xml formatted data I'll be
//using in a long annoying line, I'll include it
//separately for clarity
xmlStringBuilder.append(appendage);
ByteArrayInputStream input = new ByteArrayInputStream(xmlStringBuilder.toString().getBytes("UTF-8"));
System.out.println("Test Results:");
System.out.println();
Document doc = builder.parse(input);
Element root = doc.getDocumentElement();
NodeList children = root.getChildNodes();
System.out.println(root.getTagName());
System.out.println();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (child instanceof Element) {
Element childElement = (Element) child;
System.out.println(childElement.getTagName() + " " + childElement);
NodeList grandChildren = child.getChildNodes();
for (int x = 0; x < grandChildren.getLength(); x++) {
Node grandChild = grandChildren.item(x);
if (grandChild instanceof Element) {
Element grandChildElement = (Element) grandChild;
System.out.print("\t" + grandChildElement.getTagName() + ":\t");
NodeList greatGrandChildren = grandChild.getChildNodes();
for (int y = 0; y < greatGrandChildren.getLength(); y++) {
Node greatGrandChild = greatGrandChildren.item(y);
if (greatGrandChild instanceof Element) {
Element greatGrandChildElement = (Element) greatGrandChild;
System.out.print(" " + greatGrandChildElement.getTextContent());
if ( y < greatGrandChildren.getLength() - 1) { System.out.print(","); } }
}
System.out.println();
}
}
}
}
}
}
And here's the appendage variable in full:
String appendage = "<?xml version=\"1.0\"?><branch0><name>business</name><taxINFO/><personnel><executives><name>Billy Bob</name><name>Colonel Jessup</name></executives><managerial/><operations><name>sabrina</name><name>lisa</name></operations><services><name>jamie</name><name>justin</name><name>forest</name></services></personnel><regions><ebay><area>OK</area><area>BE</area><area>EV</area><area>WC</area></ebay><sbay><area>SJ</area><area>MP</area><area>SV</area><area>MV</area></sbay><S.F.><area>SF</area></S.F.><N.Y.><area>NY</area></N.Y.><S.CA><area>SD</area><area>LA</area></S.CA></regions><products/><services/></branch0>";
or:
String appendage = "
<?xml version=\"1.0\"?>
<branch0>
<name>business</name>
<taxINFO/>
<personnel>
<executives>
<name>Billy Bob</name>
<name>Colonel Jessup</name>
</executives>
<managerial/>
<operations>
<name>sabrina</name>
<name>lisa</name>
</operations>
<services>
<name>jamie</name>
<name>justin</name>
<name>forest</name>
</services>
</personnel>
<regions>
<ebay>
<area>OK</area>
<area>BE</area>
<area>EV</area>
<area>WC</area>
</ebay>
<sbay>
<area>SJ</area>
<area>MP</area>
<area>SV</area>
<area>MV</area>
</sbay>
<S.F.>
<area>SF</area>
</S.F.>
<N.Y.>
<area>NY</area>
</N.Y.>
<S.CA>
<area>SD</area>
<area>LA</area>
</S.CA>
</regions>
<products/>
<services/>
</branch0>";
";
And, finally my console output (which you'll see is stating [name: null] where I'd like it to say something like [name: business] or even just business; but not include the sub element data w/out whitespace):
Test Results:
branch0
name [name: null]
taxINFO [taxINFO: null]
personnel [personnel: null]
executives: Billy Bob, Colonel Jessup
managerial:
operations: sabrina, lisa
services: jamie, justin, forest
regions [regions: null]
ebay: OK, BE, EV, WC
sbay: SJ, MP, SV, MV
S.F.: SF
N.Y.: NY
S.CA: SD, LA
products [products: null]
services [services: null]
and here's my console output using .getTextContent:
Test Results:
business
branch0
name business
taxINFO
personnel Billy BobColonel Jessupsabrinalisajamiejustinforest
executives: Billy Bob, Colonel Jessup
managerial:
operations: sabrina, lisa
services: jamie, justin, forest
regions OKBEEVWCSJMPSVMVSFNYSDLA
ebay: OK, BE, EV, WC
sbay: SJ, MP, SV, MV
S.F.: SF
N.Y.: NY
S.CA: SD, LA
products
services
System.out.println(childElement.getTagName() + " " + childElement);
should be (as you actually know!)
System.out.println(childElement.getTagName() + " "
+ childElement.getTextContent());
So, for my purposes, I was able to get the individual elements I was looking for using an XPath:
XPathFactory xpfactory = XPathFactory.newInstance();
XPath path = xpfactory.newXPath();
try {
String aString = path.evaluate("/branch0/name", doc);
System.out.println(aString);
} catch (XPathExpressionException e) { e.printStackTrace(); }
Of course this requires pre-existing knowledge of the structure, but since I can validate with an XML Schema and my docs are not too complicated/heavily nested I don't think that will be an issue for me. When I finish working on my current project I'll try to look up and post links about iterating over the child nodes and checking for text nodes (as #Ian Roberts suggested) but I don't know enough about XML to do that now.

JAVA : Parsing the Xml Value using javax.ml and Xpath option

I have an XmlParserClass to get values from the xml file which looks like this.
<?xml version="1.0" encoding="utf-8" ?>
<HomePageData>
<LogoTopLeft>//*[#id='corp_logo']</LogoTopLeft>
<SingInLink>//*[#id='login']</SingInLink>
<SingUpLink>//*[#id='signup']</SingUpLink>
</HomePageData>
And the method in my class file looks like this:
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(file);
XPath xp = XPathFactory.newInstance().newXPath();
try{
String value = xp.evaluate("/LogoTopLeft/text()", doc);
return value;
} catch(XPathExpressionException e)
{
return null;
}
I am not able to get the expected data from the xml file using this class file. It just reaches the try block and then come to the catch to return "null". Most of the question in stackoverflow has been answered with a for loop to collect all nodes, but I need to take one data at a time not allelements at one stretch and Also, I need to return this value to another class file which will accept only STRINGS and so I cant pass NodeList or any other elements
P.S - The xml file is present in a different location other than the parsefile. I stored the class path value "/projName/src/com/core/path/indexPage.xml" in a file and passed it.
You just need to fix your XPath. /LogoTopLeft is looking for the element at XML root whereas it's a child element. So, either use //LogoTopLeft or specify the full path as /HomePageData/LogoTopLeft
String logo = xp.evaluate("//LogoTopLeft/text()", doc);
String signIn = xp.evaluate("//SignInLink/text()", doc);
String signUp = xp.evaluate("//SignUpLink/text()", doc);
System.out.println( "logo = " + logo +
"; signIn = " + signIn +
"; signUp = " + signUp);
/* prints:
logo = //*[#id='corp_logo']; signIn = //*[#id='login']; signUp = //*[#id='signup']
*/
EDIT : (My Test Code)
Document doc = DocumentBuilderFactory.newInstance()
.newDocumentBuilder().parse(new File("input.xml"));
XPath xp = XPathFactory.newInstance().newXPath();
try {
String logo = xp.evaluate("/HomePageData/LogoTopLeft/text()", doc);
String signIn = xp.evaluate("//SignInLink/text()", doc);
String signUp = xp.evaluate("//SignUpLink/text()", doc);
System.out.println( "logo = " + logo +
"; signIn = " + signIn +
"; signUp = " + signUp);
} catch (XPathExpressionException e) {
e.printStackTrace();
}
input.xml (placed in project directory)
<?xml version="1.0" encoding="utf-8" ?>
<HomePageData>
<LogoTopLeft>//*[#id='corp_logo']</LogoTopLeft>
<SignInLink>//*[#id='login']</SignInLink>
<SignUpLink>//*[#id='signup']</SignUpLink>
</HomePageData>

Categories

Resources