I am creating an app that parses some XML and display it in a ListView. A few items in my xml contain &s so I have escaped them like this & It is working correctly on a few devices and on the emulator.
But on two devices (Samsung Sidekick 4g API 2.2, and Samsung Replish API 2.3.6) it is failing. Everything after the & gets magically disappeared.
Here is the item in the XML giving me trouble:
<site>
<name>English Language & Usage</name>
<link>http://english.stackexchange.com/</link>
<about>English Language & Usage Stack Exchange is a question and answer site for linguists, etymologists, and serious English language enthusiasts.</about>
<image>https://dl.dropboxusercontent.com/u/5724095/XmlParseExample/english.png</image>
</site>
Here is the "meat" of the parsing code:
private static String getValue(Element item, String str) {
NodeList n = item.getElementsByTagName(str);
Log.i("StackSites", ""+getElementValue(n.item(0)));
return getElementValue(n.item(0));
}
private static String getElementValue( Node elem ) {
Node child;
if( elem != null){
if (elem.hasChildNodes()){
for( child = elem.getFirstChild(); child != null; child = child.getNextSibling() ){
if( child.getNodeType() == Node.TEXT_NODE ){
return child.getNodeValue();
}
}
}
}
return "";
}
On some devices (LG Optimus G, Moto Attrix 2, and a few emulators) this works correctly and comes out like this:
However on the two Samsung devices that I've tried getValue() method returns only the text that comes before the & so the result is this:
That's because you aren't looking at the rest of the nodes. The entity gets a different node, and the text following the entity gets the node after that. You are returning immediately -- you need to concatenate your results.
This is a known bug on some Android releases. It was fixed in Honeycomb (3.0).
There's no good work-around. You need to process the text as [text node] [entity node] [text node], interpret the entity reference yourself, and concatenate the results.
Alternatively, you can avoid the use of XML character reference and substitute your own escape sequences. As long as the parser doesn't see a &, the problem is avoided.
CommonsWare got me pointed in the right direction.
I changed the getElementValue() method to this:
private static String getElementValue( Node elem ) {
StringBuilder value = new StringBuilder();
Node child;
if( elem != null){
if (elem.hasChildNodes()){
for( child = elem.getFirstChild(); child != null; child = child.getNextSibling() ){
if( child.getNodeType() == Node.TEXT_NODE ){
value.append(child.getNodeValue());
}
}
return value.toString();
}
}
return "";
}
and it gets the second half of the text correctly now.
Related
As part of my assignment , I am given an expression tree and I need to convert it to a in-fix with O(n) run-time.
For example,
To convert this tree to "( ( 1 V ( 2 H ( 3 V 4 ) ) ) H ( 5 V 6 ) )".
I couldn't think of a way to convert it straight to infix so I thought of first converting it to post-fix and than to in-fix. (If there's a better way please tell me).
Now, my problem is with converting the tree to post-order.
I have tried the following:
private String treeToPost(Node node, String post) {
if (node != null) {
treeToPost(node.left, post);
treeToPost(node.right, post);
post = post + node.data.getName();
}
return post;
}
Now I have two problems with this method, the first one is that doesn't work because it only saves the last node it traveled, the second one is that I'm not sure it will run at O(n) because it will have to create a new string each time.
I found a solution to this issue here but it used StringBuilder which I am not allowed to use. I thought of making an array of chars to save the data , but because I dont know the size of the tree I cant know the size of the needed array.
Thank you for your time :)
Going directly to infix is probably easier, just always add parenthesis.
Secondly, doing something like this will save all nodes:
private String treeToPost(Node node) {
String returnString = "";
if (node != null) {
returnString += treeToPost(node.left);
returnString += treeToPost(node.right);
returnString += node.data.getName();
}
return returnString;
}
For infix, this should work
private String treeToPost(Node node) {
String returnString = "";
if (node != null) {
returnString += "(" + treeToPost(node.left);
returnString += node.data.getName();
returnString += treeToPost(node.right) + ")";
}
return returnString;
}
These both make new String objects each time. So i think it technically is O(n^2), because the string grows each time, but no professor of mine would deduct points for that.
However if you want to avoid this behaviour and can't use StringBuilder. You can use a CharArrayWriter. This is a buffer that grows dynamically. You can then make two methods. One that appends to the buffer and returns nothing. And one that returns a String. You would then call the buffer one from inside the String one.
I need to return the number of occurrences of the given tag, for example, a user will provide a link to an xml file and the name of the tag to find and it will return the number of occurrences of that specific tag. My code so far only works for the child of the parent node, whereas I need to check all the child of the child nodes as well, and I quite don't understand how to iterate through all of the elements of the xml file.
Modify your code to make use of recursion properly. You need to ALWAYS recurse, not only if a tag has the name you are looking for, because the children still might have the name you are looking for. Also, you need to add the result of the recursive call to the sum. Something like this:
private static int tagCount(XMLTree xml, String tag) {
assert xml != null : "Violation of: xml is not null";
assert tag != null : "Violation of: tag is not null";
int count = 0;
if (xml.isTag()) {
for (int i = 0; i < xml.numberOfChildren(); i++) {
if (xml.child(i).label().equals(tag)) {
count++;
}
count = count + tagCount(xml.child(i), tag);
}
}
return count;
}
I saw the line below in code for a DOM parser at this tutorial.
doc.getDocumentElement().normalize();
Why do we do this normalization ?
I read the docs but I could not understand a word.
Puts all Text nodes in the full depth of the sub-tree underneath this Node
Okay, then can someone show me (preferably with a picture) what this tree looks like ?
Can anyone explain me why normalization is needed?
What happens if we don't normalize ?
The rest of the sentence is:
where only structure (e.g., elements, comments, processing instructions, CDATA sections, and entity references) separates Text nodes, i.e., there are neither adjacent Text nodes nor empty Text nodes.
This basically means that the following XML element
<foo>hello
wor
ld</foo>
could be represented like this in a denormalized node:
Element foo
Text node: ""
Text node: "Hello "
Text node: "wor"
Text node: "ld"
When normalized, the node will look like this
Element foo
Text node: "Hello world"
And the same goes for attributes: <foo bar="Hello world"/>, comments, etc.
In simple, Normalisation is Reduction of Redundancies.
Examples of Redundancies:
a) white spaces outside of the root/document tags(...<document></document>...)
b) white spaces within start tag (<...>) and end tag (</...>)
c) white spaces between attributes and their values (ie. spaces between key name and =")
d) superfluous namespace declarations
e) line breaks/white spaces in texts of attributes and tags
f) comments etc...
As an extension to #JBNizet's answer for more technical users here's what implementation of org.w3c.dom.Node interface in com.sun.org.apache.xerces.internal.dom.ParentNode looks like, gives you the idea how it actually works.
public void normalize() {
// No need to normalize if already normalized.
if (isNormalized()) {
return;
}
if (needsSyncChildren()) {
synchronizeChildren();
}
ChildNode kid;
for (kid = firstChild; kid != null; kid = kid.nextSibling) {
kid.normalize();
}
isNormalized(true);
}
It traverses all the nodes recursively and calls kid.normalize()
This mechanism is overridden in org.apache.xerces.dom.ElementImpl
public void normalize() {
// No need to normalize if already normalized.
if (isNormalized()) {
return;
}
if (needsSyncChildren()) {
synchronizeChildren();
}
ChildNode kid, next;
for (kid = firstChild; kid != null; kid = next) {
next = kid.nextSibling;
// If kid is a text node, we need to check for one of two
// conditions:
// 1) There is an adjacent text node
// 2) There is no adjacent text node, but kid is
// an empty text node.
if ( kid.getNodeType() == Node.TEXT_NODE )
{
// If an adjacent text node, merge it with kid
if ( next!=null && next.getNodeType() == Node.TEXT_NODE )
{
((Text)kid).appendData(next.getNodeValue());
removeChild( next );
next = kid; // Don't advance; there might be another.
}
else
{
// If kid is empty, remove it
if ( kid.getNodeValue() == null || kid.getNodeValue().length() == 0 ) {
removeChild( kid );
}
}
}
// Otherwise it might be an Element, which is handled recursively
else if (kid.getNodeType() == Node.ELEMENT_NODE) {
kid.normalize();
}
}
// We must also normalize all of the attributes
if ( attributes!=null )
{
for( int i=0; i<attributes.getLength(); ++i )
{
Node attr = attributes.item(i);
attr.normalize();
}
}
// changed() will have occurred when the removeChild() was done,
// so does not have to be reissued.
isNormalized(true);
}
Hope this saves you some time.
I want to validate if a XML (in a String object) is well formed. Like this:
"<root> Hello StackOverflow! <a> Something here </a> Goodbye StackOverflow </root>"
It should also validate attributes, but I'm kind of too far of that right now. I just want to make sure I have the logic right. Here's what I've got so far, but I'm stucked and I need some help.
public boolean isWellFormed( String str )
{
boolean retorno = true;
if ( str == null )
{
throw new NullPointerException();
}
else
{
this.chopTheElements( str );
this.chopTags();
}
return retorno;
}
private void chopTags()
{
for ( String element : this.elements )
{
this.tags.add( element.substring( 1, element.length()-1 ) );
}
}
public void chopTheElements( String str )
{
for ( int i = 0; i < str.length(); i++ )
{
if ( str.charAt( i ) == '<' )
{
elements.add( getNextToken( str.substring( i ) ) );
}
}
}
private String getNextToken( String str )
{
String retStr = "";
if ( str.indexOf( ">" ) != -1 )
{
retStr = str.substring( 0, str.indexOf( ">" ) + 1 );
}
return retStr;
}
So far I chopped the elements like "" in a list, and then the tags in another, like this: root, /root.
But I don't know how to proceed or if I'm going in the right direction. I been asigned to solve this without regex.
Any advice? I'm lost here. Thanks.
Starting by breaking the string when you see a "<" is not the way to go about it, because the chunks you identify will be unrelated to the hierarchic structure of the XML. For example, if you have as input:
<a>xxx<b>...</b>yyy</a>
then one of your chunks will be "/b>yyy<" which isn't a useful thing to break up further.
You need to structure your code according to the structure of the grammar. If the grammar says that an element consists of a start tag then a sequence of (elements or characters) then an end tag, then you need a method that matches that sequence, and calls other methods to process its components. Because the grammar is recursive, your code will be recursive, so this is known as recursive descent parsing. It's something that is often taught in computer science courses so you'll find excellent coverage of the topic in textbooks.
If you're not dealing with a huge XML file, consider DOM parsers for your purpose. I would suggest that you look at DocumentBuilder class for this purpose. You would actually need to call the different parse() methods (your source can be a file or any other InputSource)
I am processing an xml document and reading value from it. One of the value that am reading has / in it. This is how the value looks: M/S John Smith At 4. I was doing some testing on emulator and it was showing the correct value. Now i deployed my app to my Samsung Galaxy S2 device and the process is not reading the value correctly. It just shows M in the value field for that name.
I am thinking it could be because / is a special character. Is there something i can do to escape the special character in the value and read the whole name as it is?
P.S.: I am not an experienced Java Developer so this question may sound stupid to you but if you have the solution, please let me know.
When i am printing the value in console window, this is how it reads in the xmlDocument after parsing it: M/S John Smith At 4
This function reads the value:
public static String getCharacterDataFromElement(Element e) {
Node child = e.getFirstChild();
if (child instanceof CharacterData) {
CharacterData cd = (CharacterData) child;
return cd.getData();
}
return "";
}
In the adove function, cd.getdata() returns M
After some more debugging:
When i see the element in the watch window, for other names it has only one child. But for the element that contains / it got 3 children. It slices the stringbuffer bcz it sees / in there. I guess either i have to change the below function and ready all the child nodes or i have to use escape character in there before i pass it on.
If we're talking about a text node, have you tried Node's getNodeValue()?
public static String getCharacterDataFromElement(Element e)
{
Node child = e.getFirstChild();
return child.getNodeValue();
}
Documentation: http://docs.oracle.com/javase/1.4.2/docs/api/org/w3c/dom/Node.html#getNodeValue%28%29
This is what the new method now looks like:
public static String getCharacterDataFromElement(Element e) {
return e.getTextContent();
}
As of now this is working. I dont know for how long but hopefully till i decide to do the right thing and iterate over child nodes and concatenate the string values.