I have the following code which works but I just want to know if it is possible in Jsoup to pinpoint the exact cause of error.
The following returns true (as expected)
private void validateProtocol() {
String html = "<p><a href='https://example.com/'>Link</a></p>";
Whitelist whiteList = Whitelist.basic();
whiteList.addProtocols("a","href","tel");
whiteList.removeProtocols("a","href","ftp");
boolean safe = Jsoup.isValid(html, whiteList);
System.out.println(safe);
}
When I change the above string to it returns false(as expected)
String html = "<p><a href='ftp://example.com/'>Link</a></p>";
Now when I have the following code, there are two errors one is an invalid protocol and one is the onfocus() link.
private void validateProtocol() {
String html = "<p><a href='ftp://example.com/' onfocus='invalidLink()'>Link</a></p>";
Whitelist whiteList = Whitelist.basic();
whiteList.addProtocols("a","href","tel", "device");
whiteList.removeProtocols("a","href","ftp");
boolean safe = Jsoup.isValid(html, whiteList);
System.out.println(safe);
}
The result is false but is there any way to figure out which part of the URL is false?
for example - wrong protocol or wrong method..?
You want to create a custom whitelist with reporting feature.
MyReportEnabledWhitelist.java
public class MyReportEnabledWhitelist extends Whitelist {
private Set<String> alreadyCheckedAttributeSignatures = new HashSet<>();
#Override
protected boolean isSafeTag(String tag) {
boolean isSafe = super.isSafeTag(tag);
if (!isSafe) {
say("Disallowed tag: " + tag);
}
return isSafe;
}
#Override
protected boolean isSafeAttribute(String tagName, Element el, Attribute attr) {
boolean isSafe = super.isSafeAttribute(tagName, el, attr);
String signature = el.hashCode() + "-" + attr.hashCode();
if (alreadyCheckedAttributeSignatures.contains(signature) == false) {
alreadyCheckedAttributeSignatures.add(signature);
if (!isSafe) {
say("Wrong attribute: " + attr.getKey() + " (" + attr.html() + ") in " + el.outerHtml());
}
}
return isSafe;
}
}
SAMPLE CODE
String html = "<p><a href='ftp://example.com/' onfocus='invalidLink()'>Link</a></p><a href='ftp://example2.com/'>Link 2</a>";
// * Custom whitelist
Whitelist myReportEnabledWhitelist = new MyReportEnabledWhitelist()
// ** Basic whitelist (from Jsoup)
.addTags("a", "b", "blockquote", "br", "cite", "code", "dd", "dl", "dt", "em", "i", "li", "ol", "p", "pre", "q", "small", "span",
"strike", "strong", "sub", "sup", "u", "ul") //
.addAttributes("a", "href") //
.addAttributes("blockquote", "cite") //
.addAttributes("q", "cite") //
.addProtocols("a", "href", "ftp", "http", "https", "mailto") //
.addProtocols("blockquote", "cite", "http", "https") //
.addProtocols("cite", "cite", "http", "https") //
.addEnforcedAttribute("a", "rel", "nofollow") //
// ** Customizations
.addTags("body") //
.addProtocols("a", "href", "tel", "device") //
.removeProtocols("a", "href", "ftp");
boolean safeCustom = Jsoup.isValid(html, myReportEnabledWhitelist);
System.out.println(safeCustom);
OUTPUT
Wrong attribute: href (href="ftp://example.com/") in Link
Wrong attribute: onfocus (onfocus="invalidLink()") in Link
Wrong attribute: href (href="ftp://example2.com/") in Link 2
false
Related
I need your help because I don't find the solution in Java for my problem.
I stored in object LinkedHashMap<String, String> this content:
TAG1.TAG2.TAG11 : value1
TAG1.TAG2.TAG12 : value2
TAG1.TAG2.TAG3.TAG131 : value3
TAG1.TAG2.TAG3.TAG132 : value4
TAG1.TAG2.TAG3.TAG133 : value5
TAG1.TAG2.TAG3.TAG134 : value6
TAG1.TAG4.TAG5.TAG21 : value7
TAG1.TAG4.TAG5.TAG22 : value8
TAG1.TAG4.TAG5.TAG23 : value9
TAG6 : value10
I need to display if a tag has 2 or more children, the list of child.
Here is the expected result:
TAG1.TAG2
TAG11 : value1
TAG12 : value2
TAG1.TAG2.TAG3
TAG131 : value3
TAG132 : value4
TAG133 : value5
TAG134 : value6
TAG1.TAG4.TAG5
TAG21 : value7
TAG22 : value8
TAG23 : value9
TAG6 : value10
EDIT 14/06/2022 :
In fact, my original analyse is bad because initialy I have a XML file :
<TAG1>
<TAG2>
<TAG11>value1</TAG11>
<TAG12>value2</TAG12>
<TAG3>
<TAG131>value3</TAG131>
<TAG132>value4</TAG132>
<TAG133>value5</TAG133>
<TAG134>value6</TAG134>
</TAG3>
</TAG2>
<TAG4>
<TAG5>
<TAG21>value7</TAG21>
<TAG22>value8</TAG22>
<TAG23>value9</TAG23>
</TAG5>
</TAG4>
</TAG1>
<TAG6>value10</TAG6>
And I created a map to store it :
TAG1.TAG2.TAG11 : value1
TAG1.TAG2.TAG12 : value2
TAG1.TAG2.TAG3.TAG131 : value3
TAG1.TAG2.TAG3.TAG132 : value4
TAG1.TAG2.TAG3.TAG133 : value5
TAG1.TAG2.TAG3.TAG134 : value6
TAG1.TAG4.TAG5.TAG21 : value7
TAG1.TAG4.TAG5.TAG22 : value8
TAG1.TAG4.TAG5.TAG23 : value9
TAG6 : value10
But, today I have a this case :
<TAG1>
<TAG2>
<TAG11>value1</TAG11>
<TAG12>value2</TAG12>
<TAG3>
<TAG131>value3</TAG131>
<TAG132>value4</TAG132>
<TAG133>value5</TAG133>
<TAG134>value6</TAG134>
</TAG3>
<TAG3>
<TAG131>value11</TAG131>
<TAG132>value12</TAG132>
<TAG133>value13</TAG133>
<TAG134>value14</TAG134>
</TAG3>
</TAG2>
<TAG4>
<TAG5>
<TAG21>value7</TAG21>
<TAG22>value8</TAG22>
<TAG23>value9</TAG23>
</TAG5>
</TAG4>
</TAG1>
<TAG6>value10</TAG6>
But the Map object does not allow to store many keys (in the example many TAG3). Have you got an idea how I can resolve this problem ?
EDIT 15/06/2022 :
In fact the expected result needs to keep the original XML structure.
Here the result of last sample :
TAG1.TAG2
TAG11 : value1
TAG12 : value2
TAG1.TAG2.TAG3
TAG131 : value3
TAG132 : value4
TAG133 : value5
TAG134 : value6
TAG1.TAG2.TAG3
TAG131 : value11
TAG132 : value12
TAG133 : value13
TAG134 : value14
TAG1.TAG4.TAG5
TAG21 : value7
TAG22 : value8
TAG23 : value9
TAG6 : value10
It's to display xml more human reader.
EDIT 04/07/2022 :
I detect a problem of inconsistent with "new TreeMap<>(Comparator.comparingInt(MyTag::getAppearanceOrder)". Indeed, some MyTag object are the same AppearanceOrder, so there is a problem of inconsistent ordering. Some value in Map are so removed.
To resolve I used :
map.entrySet().stream().sorted(Map.Entry.comparingByKey(. . .))
And I store the result in Map with collect().
Below the working code :
public class Main {
public static void main(String[] args) throws ParserConfigurationException, IOException, SAXException {
//Accessing the xml file
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(new File("data.xml"));
document.getDocumentElement().normalize();
Element root = document.getDocumentElement();
//Retrieving a List of records where each record contains: the original chain of tags, the numbered chain of tags and the value
String tagSep = ".";
List<Record> listRecords = new ArrayList<>();
visitXMLFile(listRecords, root.getChildNodes(), tagSep, "", "", new HashMap<>());
//Queue sorted by the numbered tag's length in descending order (from the longest to the shortest)
PriorityQueue<Record> queue = new PriorityQueue<>(Comparator.comparing(Record::getTagNumberedLen).reversed());
queue.addAll(listRecords);
//Using a set to have unique numbered tags (no duplicates) to group by in the resulting map
Set<MyTag> setMyTags = new HashSet<>();
//Checking for each numbered tag if its largest substring is equal to any other numbered tag's beginning:
// - if it does, then the substring is collected as a key to group by within the final map
//
// - if it doesn't, then another substring is generated from the previous substring until a matching value is found.
// If no value is found, then the numbered tag is collected entirely as a key for the resulting map.
while (!queue.isEmpty()) {
Record rec = queue.poll();
//This loop keeps creating substrings of the current numbered tag until:
// - the substring matches another numbered tag's beginning
// - or no more substrings can be generated
int lastIndexTagNum = rec.getTagNumbered().lastIndexOf(tagSep);
int lastIndexTag = rec.getTag().lastIndexOf(tagSep);
while (lastIndexTagNum > 0) {
//Checking if the substring matches the beginning of any numbered tag except the current one
String subStrTagNum = rec.getTagNumbered().substring(0, lastIndexTagNum);
if (listRecords.stream().anyMatch(r -> !r.getTagNumbered().equals(rec.getTagNumbered()) && r.getTagNumbered().startsWith(subStrTagNum + tagSep))) {
String subStrTag = rec.getTag().substring(0, lastIndexTag);
int appearanceOrder = listRecords.stream().filter(r -> r.getTagNumbered().startsWith(subStrTagNum + tagSep)).map(r -> r.getAppearanceOrder()).min(Comparator.naturalOrder()).orElse(0);
//If a match is found then the current substring is added to the set and the substring iteration is interrupted
setMyTags.add(new MyTag(subStrTag, subStrTagNum + tagSep, appearanceOrder));
break;
}
//Creating a new substring from the previous substring if no match has been found
lastIndexTagNum = rec.getTagNumbered().substring(0, lastIndexTagNum).lastIndexOf(tagSep);
lastIndexTag = rec.getTag().substring(0, lastIndexTag).lastIndexOf(tagSep);
}
//If no substrings of the current numbered tag matches the beginning of any other numbered tag,
//then the current numbered tag is collected as a key for the resulting map
if (lastIndexTagNum < 0) {
int appearanceOrder = listRecords.stream().filter(r -> r.getTagNumbered().startsWith(rec.getTagNumbered())).map(r -> r.getAppearanceOrder()).min(Comparator.naturalOrder()).orElse(0);
setMyTags.add(new MyTag(rec.getTag(), rec.getTagNumbered(), appearanceOrder));
}
}
//Creating a temporary resulting map (not sorted as the input)
Map<MyTag, List<String>> mapTemp = listRecords.stream()
.collect(Collectors.toMap(
rec -> {
//Looking for the longest numbered tag which matches the beginning of the current record's numbered tag.
//The reason why we need the longest match (i.e. the most accurate) is because some elements
//may share the same parents but be on different levels, for example the values 3, 4, 5 and 6
//have a key whose beginning matches both "TAG1.TAG2" and "TAG1.TAG2.TAG3", but only the longest
//match is actually the right one.
return setMyTags.stream().filter(mt -> rec.getTagNumbered().startsWith(mt.getTagNumbered())).max(Comparator.comparingInt(MyTag::getTagNumberedLen)).orElseThrow(() -> new RuntimeException("No key found"));
},
rec -> {
//Retrieving, like above, the numbered tag that will be used to map the current value
MyTag myTag = setMyTags.stream().filter(mt -> rec.getTagNumbered().startsWith(mt.getTagNumbered())).max(Comparator.comparingInt(MyTag::getTagNumberedLen)).orElseThrow(() -> new RuntimeException("No key found"));
//If the new numbered tag and the record's numbered tag are equal then a List with the current value is returned
if (myTag.getTagNumbered().equals(rec.getTagNumbered())) {
return new ArrayList<>(List.of(rec.getValue()));
} else { //If the new numbered tag is a substring of the record's numbered tag then the rest of the current (non-numbered) tag is added to the value
return new ArrayList<>(List.of(rec.getTag().substring(myTag.getTag().length() + 1) + " : " + rec.getValue()));
}
},
//Handling colliding cases by merging the lists together
(list1, list2) -> {
list1.addAll(list2);
return list1;
}
)
);
//Creating a TreeMap whose ordering is based on the insertion order of the input
Map<MyTag, List<String>> mapRes =
mapTemp.entrySet().stream()
.sorted(Map.Entry.comparingByKey(Comparator.comparingInt(MyTag::getAppearanceOrder)))
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
//Printing the resulting map
for (Map.Entry<MyTag, List<String>> entry : mapRes.entrySet()) {
System.out.println(entry.getKey());
for (String value : entry.getValue()) {
System.out.println("\t" + value);
}
}
}
private static void visitXMLFile(List<Record> listInput, NodeList nodeList, String tagSep, String tag, String tagNumbered, Map<String, Integer> mapTagOccurrence) {
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node.hasChildNodes()) {
String newTag = tag.isEmpty() ? node.getNodeName() : tag + tagSep + node.getNodeName();
//Setting or incrementing the number of appearances of a tag chain
//(sometimes a same chain of tags can be repeated, ex: TAG1.TAG2.TAG3)
if (!mapTagOccurrence.containsKey(newTag)) {
mapTagOccurrence.put(newTag, 1);
} else {
mapTagOccurrence.computeIfPresent(newTag, (key, val) -> val + 1);
}
//Creating a numbered version of the tag where its number of appearances is added at the end.
//This is done to uniquely identify different groups of tag chain when these are repeated (ex: TAG1.TAG2.TAG3)
String newTagNum = tagNumbered.isEmpty() ? node.getNodeName() + mapTagOccurrence.get(newTag) : tagNumbered + tagSep + node.getNodeName() + mapTagOccurrence.get(newTag);
visitXMLFile(listInput, node.getChildNodes(), tagSep, newTag, newTagNum, mapTagOccurrence);
} else {
if (!node.getTextContent().trim().equals("")) {
int appearanceOrder = listInput.size() + 1;
listInput.add(new Record(tag, tagNumbered, node.getTextContent().trim(), appearanceOrder));
}
}
}
}
}
class MyTag {
//Tag chain for the user
private String tag;
//Unique tag chain for identification
private String tagNumbered;
private int appearanceOrder;
public MyTag(String tag, String tagNumbered, int appearanceOrder) {
this.tag = tag;
this.tagNumbered = tagNumbered;
this.appearanceOrder = appearanceOrder;
}
public String getTag() {
return tag;
}
public String getTagNumbered() {
return tagNumbered;
}
public int getTagNumberedLen() {
return tagNumbered == null ? 0 : tagNumbered.length();
}
public int getAppearanceOrder() {
return appearanceOrder;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MyTag tagPair = (MyTag) o;
return Objects.equals(tagNumbered, tagPair.tagNumbered);
}
#Override
public int hashCode() {
return Objects.hash(tagNumbered);
}
#Override
public String toString() {
return tag;
}
}
class Record {
//Tag chain for the user
private String tag;
//Unique tag chain for identification
private String tagNumbered;
private String value;
private int appearanceOrder;
public Record(String tag, String tagNumbered, String value, int appearanceOrder) {
this.tag = tag;
this.tagNumbered = tagNumbered;
this.value = value;
this.appearanceOrder = appearanceOrder;
}
public String getTag() {
return tag;
}
public String getTagNumbered() {
return tagNumbered;
}
public int getTagNumberedLen() {
return tagNumbered == null ? 0 : tagNumbered.length();
}
public String getValue() {
return value;
}
public int getAppearanceOrder() {
return appearanceOrder;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Record record = (Record) o;
return Objects.equals(tagNumbered, record.tagNumbered);
}
#Override
public int hashCode() {
return Objects.hash(tagNumbered);
}
#Override
public String toString() {
return tag + " - " + tagNumbered + " - " + value;
}
}
Edit
At this point to answer your edited question, I had to use a List instead of a Map to store the input since multiple values share the same key and a Map<String, List<String>> wouldn't maintain the insertion order. In fact, the values from 3 to 6 would be alternated with the values from 11 to 14.
Besides, since the same chain of tags can appear several times (ex: TAG1.TAG2.TAG3), I had to implement two custom classes: MyTag and Record.
The first class represents a custom tag made of two fields: tag and tagNumbered. The first field holds the tag chain that must be shown to the user, while the second is used as the actual identifier to group by in the stream operation. tagNumbered is basically a copy of tag where at the end of each nested tag is added its number of appearances.
Instead, the class Record is used to represent a value accompanied by its tag chain and numbered tag chain.
So, the following XML is represented as follows by the respective classes:
<x>
<y>
<z>value1</z>
</y>
<y>
<z>value2</z>
</y>
</x>
Record:
Record1:
- tag: x.y.z
- tagNumbered: x1.y1.z1
- value: value1
Record2:
- tag: x.y.z
- tagNumbered: x1.y2.z1 //because y appears twice within x
- value: value2
MyTag (MyTag is created from Record):
MyTag1:
- tag: x.y.z
- tagNumbered: x1.y1.z1
MyTag2:
- tag: x.y.z
- tagNumbered: x1.y2.z1 //because y appears twice within x
Here is an XML sample based on your question's input, that I've used for the code below.
<root>
<TAG1>
<TAG2>
<TAG11>value1</TAG11>
<TAG12>value2</TAG12>
<TAG3>
<TAG131>value3</TAG131>
<TAG132>value4</TAG132>
<TAG133>value5</TAG133>
<TAG134>value6</TAG134>
</TAG3>
<TAG3>
<TAG131>value11</TAG131>
<TAG132>value12</TAG132>
<TAG133>value13</TAG133>
<TAG134>value14</TAG134>
</TAG3>
</TAG2>
<TAG4>
<TAG5>
<TAG21>value7</TAG21>
<TAG22>value8</TAG22>
<TAG23>value9</TAG23>
</TAG5>
</TAG4>
</TAG1>
<TAG6>value10</TAG6>
</root>
Original Answer Updated
The first part of the problem consists in creating a List<Record> while reading from the XML file which is achieved with the visitXMLFile method.
After reading the records from the file, we need to create a Set of unique numbered tag chains to identify each group of values. This is actually done with a Set<MyTag>; however MyTag's equals() and hashCode() are based exclusively on tagNumbered.
After creating the Set of unique numbered tags, we need to stream the input list of entries with a single operation: collect(Collectors.toMap()). In this operation, each record is mapped to a MyTag (i.e., a numbered tag) of the Set previously created.
Finally, to maintain the original insertion order, the resulting Map has been implemented as a TreeMap initialized with a Comparator defined on the order of the input list's records.
Here is an implementation with detailed comments explaining the whole logic step by step:
public class Main {
public static void main(String[] args) throws ParserConfigurationException, IOException, SAXException {
//Accessing the xml file
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(new File("data.xml"));
document.getDocumentElement().normalize();
Element root = document.getDocumentElement();
//Retrieving a List of records where each record contains: the original chain of tags, the numbered chain of tags and the value
String tagSep = ".";
List<Record> listRecords = new ArrayList<>();
visitXMLFile(listRecords, root.getChildNodes(), tagSep, "", "", new HashMap<>());
//Queue sorted by the numbered tag's length in descending order (from the longest to the shortest)
PriorityQueue<Record> queue = new PriorityQueue<>(Comparator.comparing(Record::getTagNumberedLen).reversed());
queue.addAll(listRecords);
//Using a set to have unique numbered tags (no duplicates) to group by in the resulting map
Set<MyTag> setMyTags = new HashSet<>();
//Checking for each numbered tag if its largest substring is equal to any other numbered tag's beginning:
// - if it does, then the substring is collected as a key to group by within the final map
//
// - if it doesn't, then another substring is generated from the previous substring until a matching value is found.
// If no value is found, then the numbered tag is collected entirely as a key for the resulting map.
while (!queue.isEmpty()) {
Record rec = queue.poll();
//This loop keeps creating substrings of the current numbered tag until:
// - the substring matches another numbered tag's beginning
// - or no more substrings can be generated
int lastIndexTagNum = rec.getTagNumbered().lastIndexOf(tagSep);
int lastIndexTag = rec.getTag().lastIndexOf(tagSep);
while (lastIndexTagNum > 0) {
//Checking if the substring matches the beginning of any numbered tag except the current one
String subStrTagNum = rec.getTagNumbered().substring(0, lastIndexTagNum);
if (listRecords.stream().anyMatch(r -> !r.getTagNumbered().equals(rec.getTagNumbered()) && r.getTagNumbered().startsWith(subStrTagNum + tagSep))) {
String subStrTag = rec.getTag().substring(0, lastIndexTag);
int appearanceOrder = listRecords.stream().filter(r -> r.getTagNumbered().startsWith(subStrTagNum + tagSep)).map(r -> r.getAppearanceOrder()).min(Comparator.naturalOrder()).orElse(0);
//If a match is found then the current substring is added to the set and the substring iteration is interrupted
setMyTags.add(new MyTag(subStrTag, subStrTagNum + tagSep, appearanceOrder));
break;
}
//Creating a new substring from the previous substring if no match has been found
lastIndexTagNum = rec.getTagNumbered().substring(0, lastIndexTagNum).lastIndexOf(tagSep);
lastIndexTag = rec.getTag().substring(0, lastIndexTag).lastIndexOf(tagSep);
}
//If no substrings of the current numbered tag matches the beginning of any other numbered tag,
//then the current numbered tag is collected as a key for the resulting map
if (lastIndexTagNum < 0) {
int appearanceOrder = listRecords.stream().filter(r -> r.getTagNumbered().startsWith(rec.getTagNumbered())).map(r -> r.getAppearanceOrder()).min(Comparator.naturalOrder()).orElse(0);
setMyTags.add(new MyTag(rec.getTag(), rec.getTagNumbered(), appearanceOrder));
}
}
//Creating a temporary resulting map (not sorted as the input)
Map<MyTag, List<String>> mapTemp = listRecords.stream()
.collect(Collectors.toMap(
rec -> {
//Looking for the longest numbered tag which matches the beginning of the current record's numbered tag.
//The reason why we need the longest match (i.e. the most accurate) is because some elements
//may share the same parents but be on different levels, for example the values 3, 4, 5 and 6
//have a key whose beginning matches both "TAG1.TAG2" and "TAG1.TAG2.TAG3", but only the longest
//match is actually the right one.
return setMyTags.stream().filter(mt -> rec.getTagNumbered().startsWith(mt.getTagNumbered())).max(Comparator.comparingInt(MyTag::getTagNumberedLen)).orElseThrow(() -> new RuntimeException("No key found"));
},
rec -> {
//Retrieving, like above, the numbered tag that will be used to map the current value
MyTag myTag = setMyTags.stream().filter(mt -> rec.getTagNumbered().startsWith(mt.getTagNumbered())).max(Comparator.comparingInt(MyTag::getTagNumberedLen)).orElseThrow(() -> new RuntimeException("No key found"));
//If the new numbered tag and the record's numbered tag are equal then a List with the current value is returned
if (myTag.getTagNumbered().equals(rec.getTagNumbered())) {
return new ArrayList<>(List.of(rec.getValue()));
} else { //If the new numbered tag is a substring of the record's numbered tag then the rest of the current (non-numbered) tag is added to the value
return new ArrayList<>(List.of(rec.getTag().substring(myTag.getTag().length() + 1) + " : " + rec.getValue()));
}
},
//Handling colliding cases by merging the lists together
(list1, list2) -> {
list1.addAll(list2);
return list1;
}
)
);
//Creating a TreeMap whose ordering is based on the insertion order of the input
Map<MyTag, List<String>> mapRes = new TreeMap<>(Comparator.comparingInt(MyTag::getAppearanceOrder));
mapRes.putAll(mapTemp);
//Printing the resulting map
for (Map.Entry<MyTag, List<String>> entry : mapRes.entrySet()) {
System.out.println(entry.getKey());
for (String value : entry.getValue()) {
System.out.println("\t" + value);
}
}
}
private static void visitXMLFile(List<Record> listInput, NodeList nodeList, String tagSep, String tag, String tagNumbered, Map<String, Integer> mapTagOccurrence) {
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node.hasChildNodes()) {
String newTag = tag.isEmpty() ? node.getNodeName() : tag + tagSep + node.getNodeName();
//Setting or incrementing the number of appearances of a tag chain
//(sometimes a same chain of tags can be repeated, ex: TAG1.TAG2.TAG3)
if (!mapTagOccurrence.containsKey(newTag)) {
mapTagOccurrence.put(newTag, 1);
} else {
mapTagOccurrence.computeIfPresent(newTag, (key, val) -> val + 1);
}
//Creating a numbered version of the tag where its number of appearances is added at the end.
//This is done to uniquely identify different groups of tag chain when these are repeated (ex: TAG1.TAG2.TAG3)
String newTagNum = tagNumbered.isEmpty() ? node.getNodeName() + mapTagOccurrence.get(newTag) : tagNumbered + tagSep + node.getNodeName() + mapTagOccurrence.get(newTag);
visitXMLFile(listInput, node.getChildNodes(), tagSep, newTag, newTagNum, mapTagOccurrence);
} else {
if (!node.getTextContent().trim().equals("")) {
int appearanceOrder = listInput.size() + 1;
listInput.add(new Record(tag, tagNumbered, node.getTextContent().trim(), appearanceOrder));
}
}
}
}
}
class MyTag {
//Tag chain for the user
private String tag;
//Unique tag chain for identification
private String tagNumbered;
private int appearanceOrder;
public MyTag(String tag, String tagNumbered, int appearanceOrder) {
this.tag = tag;
this.tagNumbered = tagNumbered;
this.appearanceOrder = appearanceOrder;
}
public String getTag() {
return tag;
}
public String getTagNumbered() {
return tagNumbered;
}
public int getTagNumberedLen() {
return tagNumbered == null ? 0 : tagNumbered.length();
}
public int getAppearanceOrder() {
return appearanceOrder;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MyTag tagPair = (MyTag) o;
return Objects.equals(tagNumbered, tagPair.tagNumbered);
}
#Override
public int hashCode() {
return Objects.hash(tagNumbered);
}
#Override
public String toString() {
return tag;
}
}
class Record {
//Tag chain for the user
private String tag;
//Unique tag chain for identification
private String tagNumbered;
private String value;
private int appearanceOrder;
public Record(String tag, String tagNumbered, String value, int appearanceOrder) {
this.tag = tag;
this.tagNumbered = tagNumbered;
this.value = value;
this.appearanceOrder = appearanceOrder;
}
public String getTag() {
return tag;
}
public String getTagNumbered() {
return tagNumbered;
}
public int getTagNumberedLen() {
return tagNumbered == null ? 0 : tagNumbered.length();
}
public String getValue() {
return value;
}
public int getAppearanceOrder() {
return appearanceOrder;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Record record = (Record) o;
return Objects.equals(tagNumbered, record.tagNumbered);
}
#Override
public int hashCode() {
return Objects.hash(tagNumbered);
}
#Override
public String toString() {
return tag + " - " + tagNumbered + " - " + value;
}
}
Output
TAG1.TAG2
TAG11 : value1
TAG12 : value2
TAG1.TAG2.TAG3
TAG131 : value3
TAG132 : value4
TAG133 : value5
TAG134 : value6
TAG1.TAG2.TAG3
TAG131 : value11
TAG132 : value12
TAG133 : value13
TAG134 : value14
TAG1.TAG4.TAG5
TAG21 : value7
TAG22 : value8
TAG23 : value9
TAG6
value10
I'am trying to get User object from json by Jayway JsonPath.
My class is:
public class User{
private String id;
private String name;
private String password;
private String email;
/*getters setters constructor*/
}
And json example:
{
"user": [
{
"id": "1",
"login": "client1",
"password": "qwerty",
"email": "client#gmail.com"
}
]
}
I wanna to get smth like this:
public Optional<User> find(String id) throws NoSuchEntityException {
Optional<User> user = Optional.empty();
try{
Path path = Path.of(fileDestination+fileName);
ReadContext ctx = JsonPath.parse(Files.readString(path));
User readUser = ctx.read("$..user[*]",User.class,Filter.filter(where("id").is(id)));
user = Optional.ofNullable(readUser);
} catch (IOException e) {
e.printStackTrace();
}
return user;
}
Or to get good advice how to code it:D
two things here
you must use the placeholder ? instead of * when you're filtering, otherwise, the filter will be ignored.
read() will return a list, not a single object.
so, I think, you need something like this
String json = "{\r\n"
+ " \"user\": [\r\n"
+ " {\r\n"
+ " \"id\": \"1\",\r\n"
+ " \"login\": \"client1\",\r\n"
+ " \"password\": \"qwerty\",\r\n"
+ " \"email\": \"client#gmail.com\"\r\n"
+ " }\r\n"
+ " ]\r\n"
+ "}";
Predicate filterById = filter(where("id").is("1"));
List<User> users = JsonPath.parse(json).read("$.user[?]",filterById );
System.out.println(users);
Reference: Filter Predicates
where("category").is("fiction").and("price").lte(10D) );
List<Map<String, Object>> books =
parse(json).read("$.store.book[?]", cheapFictionFilter);
Notice the placeholder ? for the filter in the path. When multiple filters are provided they are applied in order where the number of placeholders must match the number of provided filters. You can specify multiple predicate placeholders in one filter operation [?, ?], both predicates must match.
I have a method getFormattedValue(List<String> dataHeaders, List<String> data) that returns String value based contents of dataHeaders and data list. dataHeaders list can have 10 distinct values and based on value at particular index output string formation changes.
Below code was working fine tiil the time when dataHeaders contents are received in specific order , but lately this order is changing many atimes. As this input is received from other app/ system, i do not have control over order of elements. Hence i want to update my code so that it works correctly even after input order is altered.
If 1'st element of dataHeaders list is "OPERATION_NAME" then i need not take any action, but if "OPERATION_NAME" comes at 2'nd index in dataHeaders list then i need to do special formatting to output value. And so on....
My Pain is that i receive 10 dataHeaders. Please suggest me any good approach to handle this issue. I am posting my code below, suggestions are welcome.
import java.util.ArrayList;
import java.util.List;
public class ValueGenerator {
public String getFormattedValue(List<String> dataHeaders, List<String> data){
String formattedOutValue=null;
if(dataHeaders!=null && data!=null &&
dataHeaders.size() == data.size()){
if(dataHeaders.get(0).equals("OPERATION_NAME")){
formattedOutValue=data.get(0); // Add no spaces
}else if(dataHeaders.get(1).equals("OPERATION_NAME")){
formattedOutValue=data.get(1)+" "; // Add 4 blank spaces
}else if(dataHeaders.get(2).equals("OPERATION_NAME")){
formattedOutValue=data.get(2)+" "; // Add 6 blank spaces
}
}
//likewise i want to avoid redundant if - else if check
return formattedOutValue;
}
}
I assume that you want data.get(2) in the case of dataHeaders.get(2) and not data.get(1)
Basically you just need to find the index of the string OPERATION_NAME in dataHeaders and use it for data. The simplest way is to iterate over the list dataHeaders with an index and stop when you find it.
If you need to handle more strings then you could use a Map to map the strings you want to evaluate to their indices, this could look like this:
final static Map<Integer, String> formatByIndexMap;
static {
formatByIndexMap = new HashMap<>();
formatByIndexMap.put(0, "");
formatByIndexMap.put(1, " ");
formatByIndexMap.put(2, " ");
// and so on
formatByIndexMap.put(9, "----format for 10----");
}
public String getFormattedValue(List<String> dataHeaders, List<String> data){
String formattedOutValue = null;
if (dataHeaders != null && data != null &&
dataHeaders.size() == data.size()) {
Map<String, Integer> dhm = new HashMap<>();
for(int i = 0; i < dataHeaders.size(); i++) {
dhm.put(dataHeaders.get(i), i);
}
Integer operationNameIndex = dhm.get("OPERATION_NAME");
if(operationNameIndex != null) {
formattedOutValue = data.get(operationNameIndex.intValue()) +
formatByIndexMap.get(operationNameIndex);
}
}
return formattedOutValue;
}
The format map formatByIndexMap is defines as final static.
An for some basic testing:
#Test
public void getFormattedValue() {
List<String> data = Arrays.asList("operation", "foo", "bar");
List<String> dataHeaders;
String formated;
dataHeaders = Arrays.asList("OPERATION_NAME", "FOO_NAME", "BAR_NAME");
formated = getFormattedValue(dataHeaders, data);
Assert.assertEquals("operation", formated); // no spaces
dataHeaders = Arrays.asList("FOO_NAME", "OPERATION_NAME", "BAR_NAME");
formated = getFormattedValue(dataHeaders, data);
Assert.assertEquals("foo ", formated); // four spaces
dataHeaders = Arrays.asList("FOO_NAME", "BAR_NAME", "OPERATION_NAME");
formated = getFormattedValue(dataHeaders, data);
Assert.assertEquals("bar ", formated); // six spaces
dataHeaders = Arrays.asList("FOO_NAME", "BAR_NAME", "", "", "", "", "",
"", "", "OPERATION_NAME");
data = Arrays.asList("operation", "foo", "bar", "", "", "", "",
"", "", "tail");
formated = getFormattedValue(dataHeaders, data);
// ----format for 10----
Assert.assertEquals("tail----format for 10----", formated);
}
I'd like to consolidate my error messages and stuff into one file, and make my code more readable if possible.
Here's an example of what I have in my enum file:
public enum ZipErrorType {
// START: define exception messages (alphabetical order)
EMPTY_FILE_NAME_IN_LIST {
public String toString() {
return "One or more null/empty filename(s) found";
}
},
FILE_DOESNT_EXIST {
public String who(String sThisFile) {
return "[" + sThisFile + "] does not exist";
}
},
FILE_LIST_IS_NULL {
public String toString() {
return "File list is null/empty";
}
},
FILENAME_NOT_ABSOLUTE {
public String who(String sThisFile) {
return "[" + sThisFile + "] is not absolute";
}
},
MUST_BE_DIR {
public String who(String sThisFile) {
return "[" + sThisFile + "] must be a directory";
}
},
MUST_BE_FILE {
public String who(String sThisFile) {
return "[" + sThisFile + "] must be a file";
}
},
NULL_OR_EMPTY {
public String who(String sThisFile) {
return "[" + sThisFile + "] is null/empty";
}
},
OUTPUT_FILE_ALREADY_EXISTS {
public String who(String sThisFile) {
return "[" + sThisFile + "] already exists";
}
},
OUTPUT_FILENAME_EMPTY {
public String toString() {
return "Output filename is null/empty";
}
},
OUTPUT_PATH_EMPTY {
public String toString() {
return "Output path is null/empty";
}
},
// END: define exception messages
NONE {};
public String who(String sThisFile) { return ""; }
}
Then in my program I have code like:
private static ZipErrorType getFileErrorsIfAny(String sFilename, boolean shouldBeFile) {
// check if given filename is absolute
File file = new File(sFilename);
if (!file.isAbsolute()) {
return ZipErrorType.FILENAME_NOT_ABSOLUTE;
}
// check if file exists
if (!file.exists()) {
return ZipErrorType.FILE_DOESNT_EXIST;
}
// check if corresponding file is a file when it shouldn't be...
if (file.isFile() && !shouldBeFile) {
return ZipErrorType.MUST_BE_DIR;
}
// ...or a directory when it should be a file
else if (file.isDirectory() && shouldBeFile) {
return ZipErrorType.MUST_BE_FILE;
}
return ZipErrorType.NONE;
}
...and an example of how I make use of my enum:
// check input files
for (String sFile : files) {
if (sFile == null || sFile.trim().length() == 0) {
throw new NullPointerException("One or more filename is null/empty");
}
errorIfAny = getFileErrorsIfAny(sFile.trim(), true);
if (!errorIfAny.equals(ZipErrorType.NONE)) {
throw new ZipInputException(errorIfAny.who(sFile.trim()));
}
}
Now I know it's hard to judge just by these code snippets alone, but is this alright, from a general perspective? Is what I'm doing not worth the trouble, and is there a way to improve this?
I would suggest using simple string templates instead of enums for building error messages.
Something like this:
String EMPTY_FILE_NAME_IN_LIST_TEMPLATE = "One or more null/empty filename(s) found";
String FILE_DOESNT_EXIST_TEMPLATE = "[ %s ] does not exist";
String FILE_LIST_IS_NULL_TEMPLATE = "File list is null/empty";
String FILENAME_NOT_ABSOLUTE_TEMPLATE = "[ %s ] is not absolute";
String MUST_BE_DIR_TEMPLATE = "[ %s ] must be a directory";
String MUST_BE_FILE_TEMPLATE = "[ %s ] must be a file";
String NULL_OR_EMPTY_TEMPLATE = "[ %s ] is null/empty";
String OUTPUT_FILE_ALREADY_EXISTS_TEMPLATE = "[ %s ] already exists";
String OUTPUT_FILENAME_EMPTY_TEMPLATE = "Output filename is null/empty";
String OUTPUT_PATH_EMPTY_TEMPLATE = "Output path is null/empty";
And then, use String.format(template, sFilename) for building actual message.
You may also consider throwing an exception right out of getFileErrorsIfAny() method:
File file = new File(sFilename);
if (!file.isAbsolute()) {
throw new ZipInputException(String.format(FILENAME_NOT_ABSOLUTE_TEMPLATE, sFilename));
}
Looks cleaner and more compact to me.
This seems to have the potential to result in many many massive enums dotted around the code.
This isn't the first time someone has wanted to separate of the log message from the log statement.
In fact java.util.logging already has a framework for this that is designed for localisation.
It uses a .properties file which contains the messages.
You get the logger with the path to the file in the classpath : -
Logger logger = Logger.getLogger("com.example", "path/to/messages.properties");
Logging statements are then done using the property keys
logger.log(level, "messageKey");
And you can parameterise the logging because it uses MessageFormat syntax
zip.fileDoesNotExist={0} does not exist
logger.log(level, "zip.fileDoesNotExist", file);
These parameters are extremely flexible as you can specify formatting information in them and even use ChoiceFormat if needed.
The main advantage of all this is that your messages are in a separate file, rather than a class. And you can turn logging on and off at will with the logging.properties file. You can even turn logging on and off for single classes. And you can log to multiple files, to the console, you can send emails on errors etc etc
So, in conclusion. Use an existing logging framework. Don't roll your own.
Disclaimer: I only talk about JUL because then is built into Java - you don't need any 3rd party libs, there are many, many other frameworks out there.
I have been combing through the wiki for the jqgrid and i can't seem to figure out how to change the logic in my controller for the jqgrid to search.
I assume the search will use the same URL specified in the jqgrid. Here is the action logic called by my jqgrid. I am using spring 3.0 and its a java controller.
#RequestMapping(value = "studentjsondata", method = RequestMethod.GET)
public #ResponseBody String studentjsondata(HttpServletRequest httpServletRequest) {
Format formatter = new SimpleDateFormat("MMMM dd, yyyy");
String column = "id";
if(httpServletRequest.getParameter("sidx") != null){
column = httpServletRequest.getParameter("sidx");
}
String orderType = "DESC";
if(httpServletRequest.getParameter("sord") != null){
orderType = httpServletRequest.getParameter("sord").toUpperCase();
}
int page = 1;
if(Integer.parseInt(httpServletRequest.getParameter("page")) >= 1){
page = Integer.parseInt(httpServletRequest.getParameter("page"));
}
int limitAmount = 10;
int limitStart = limitAmount*page - limitAmount;
List<Person> students = Person.findStudentPeopleOrderByColumn(true, column, orderType, limitStart, limitAmount).getResultList();
long countStudents = Student.countStudents();
double tally = Math.ceil(countStudents/10.0d);
int totalPages = (int)tally;
long records = countStudents;
StringBuilder sb = new StringBuilder();
sb.append("{\"page\":\"").append(page).append("\", \"records\":\"").append(records).append("\", \"total\":\"").append(totalPages).append("\", \"rows\":[");
boolean first = true;
for (Person s: students) {
sb.append(first ? "" : ",");
if (first) {
first = false;
}
sb.append(String.format("{\"id\":\"%s\", \"cell\":[\"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\"]}",s.getId(), s.getId(), s.getFirstName(), s.getLastName(), formatter.format(s.getDateOfBirth().getTime()), s.getGender(), s.getMaritalStatus()));
}
sb.append("]}");
return sb.toString();
}
and here is my navGrid decleration
$("#studentGrid").jqGrid('navGrid', "#pager", {edit:false,add:false,del:false,search:true},{ },{ },{ },
{
sopt:['eq', 'ne', 'lt', 'gt', 'cn', 'bw', 'ew'],
closeOnEscape: true,
multipleSearch: true,
closeAfterSearch: true
}
);
Here is my colModel and colNames
colNames:['id','First Name', 'Last Name', 'Date Of Birth', 'Gender', 'Marital Status'],
colModel:[
{name:'id',index:'id', width:15},
{name:'firstName',index:'firstName', width:30, formoptions:{elmprefix:'(*) '}, editable:true, edittype: 'text', editrules:{required:true}},
{name:'lastName',index:'lastName', width:30, formoptions:{elmprefix:'(*) '}, editable:true, edittype: 'text',editrules:{required:true}},
{name:'dateOfBirth',index:'dateOfBirth', width:30, formoptions:{elmprefix:'(*) '},editrules:{required:true}, editable:true, edittype: 'text',
editoptions: {
dataInit: function(element) {
$(element).datepicker({dateFormat: 'MM dd, yy'})
}
}
},
{name:'gender',index:'gender', width:30, formoptions:{elmprefix:'(*) '}, editable:true, editrules:{required:true}, edittype: 'select',
editoptions:{value:{}}
},
{name:'maritalStatus',index:'maritalStatus', width:30, formoptions:{elmprefix:'(*) '}, editable:true, editrules:{required:true}, edittype: 'select',
editoptions:{value:{}}
}
]
As it is, by default the search uses trhe searchGrid method. In the post array the _search: true and filters: {"groupOp":"AND","rules":[{"field":"firstName","op":"eq","data":"Anil"}]} are present.
The searchField, searchOper and searchString are all empty but present in th epost array.
What do I have to do to get the search working?
Do I have to parse the json into Java using the json parser and the filters array, then change my query by adding a where clause and use the values form the Json object?
Does the jqgrid query its own data object insted of going back to the server and launch a new query ?
I am not too sure what I have to do, please offer some form of guidance.
I am not use Spring myself, but the post seems to me contain the information which you need.
In general if you use Advance Searching dialog (multipleSearch: true) or Toolbar Searching with stringResult: true the jqGrid send to the server additional parameter filters which format described here. The one parameter filters can contain information about multiple filters. So you have to covert JSON string to an object and analyse the object to construct some kine of WHERE part of the SELECT statement. The exact implementation is depend from the technology which you use to assess to the database.