YAML Environment Variable Interpolation in SnakeYAML scala - java

Leveraging the best from SnakeYAML & Jackson in scala, I am using the following method to parse YAML files. This method supports the usage of anchors in YAML
import com.fasterxml.jackson.databind.{JsonNode, ObjectMapper}
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import java.io.{File, FileInputStream}
import org.yaml.snakeyaml.{DumperOptions, LoaderOptions, Yaml}
/**
* YAML Parser using SnakeYAML & Jackson Implementation
*
* #param yamlFilePath : Path to the YAML file that has to be parsed
* #return: JsonNode of YAML file
*/
def parseYaml(yamlFilePath: String): JsonNode = {
// Parsing the YAML file with SnakeYAML - since Jackson Parser does not have Anchors and reference support
val ios = new FileInputStream(new File(yamlFilePath))
val loaderOptions = new LoaderOptions
loaderOptions.setAllowDuplicateKeys(false)
val yaml = new Yaml(
loaderOptions
)
val mapper = new ObjectMapper().registerModules(DefaultScalaModule)
val yamlObj = yaml.loadAs(ios, classOf[Any])
// Converting the YAML to Jackson YAML - since it has more flexibility for traversing through nodes
val jsonString = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(yamlObj)
val jsonObj = mapper.readTree(jsonString)
println(jsonString)
jsonObj
}
However, this currently does not support the interpolation of environment variables within the YAML file. Eg: If we get the following environment variables when we do
>>> println(System.getenv())
{PATH=/usr/bin:/bin:/usr/sbin:/sbin, XPC_FLAGS=0x0, SHELL=/bin/bash}
The question is how do we achieve environment variable interpolation in yaml file, lets say we have the following YAML file:
path_value: ${PATH}
xpc: ${XPC_FLAGS}
shell_path: ${SHELL}
Then after parsing the YAML should be:
{
"path_value": "/usr/bin:/bin:/usr/sbin:/sbin",
"xpc": "0x0",
"shell_path": "/bin/bash"
}
Thanks for your time & efforts to answer in advance!

Thanks to the comments & guidance from the community. Here is my solution for the parser with custom constructors and represented:
import java.io.{File, FileInputStream}
import scala.util.matching.Regex
import com.fasterxml.jackson.databind.{JsonNode, ObjectMapper}
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import org.yaml.snakeyaml.{DumperOptions, LoaderOptions, Yaml}
import org.yaml.snakeyaml.constructor.{AbstractConstruct, Constructor}
import org.yaml.snakeyaml.error.MissingEnvironmentVariableException
import org.yaml.snakeyaml.nodes.Node
import org.yaml.snakeyaml.nodes.ScalarNode
import org.yaml.snakeyaml.nodes.Tag
import org.yaml.snakeyaml.representer.Representer
/**
* Copyright (c) 2008, http://www.snakeyaml.org
* Class dedicated for SnakeYAML Support for Environment Variables
*/
/**
* Construct scalar for format ${VARIABLE} replacing the template with the value from the environment.
*
* #see Variable substitution
* #see Variable substitution
*/
class EnvScalarConstructor() extends Constructor {
val ENV_TAG = new Tag("!ENV")
this.yamlConstructors.put(ENV_TAG, new ConstructEnv)
val ENV_regex: Regex = "\\$\\{\\s*((?<name>\\w+)((?<separator>:?(-|\\?))(?<value>\\w+)?)?)\\s*\\}".r
private class ConstructEnv extends AbstractConstruct {
override def construct(node: Node) = {
val matchValue = constructScalar(node.asInstanceOf[ScalarNode])
val patternMatch = ENV_regex.findAllIn(matchValue)
val eval = patternMatch.toString()
val name = patternMatch.group(1)
val value = patternMatch.group(2)
val separator = null
apply(name, separator, if (value != null) value else "", ENV_regex.replaceAllIn(matchValue, getEnv(name)))
}
}
/**
* Implement the logic for missing and unset variables
*
* #param name - variable name in the template
* #param separator - separator in the template, can be :-, -, :?, ?
* #param value - default value or the error in the template
* #param environment - the value from the environment for the provided variable
* #return the value to apply in the template
*/
def apply(name: String, separator: String, value: String, environment: String): String = {
if (environment != null && !environment.isEmpty) return environment
// variable is either unset or empty
if (separator != null) { //there is a default value or error
if (separator == "?") if (environment == null) throw new MissingEnvironmentVariableException("Missing mandatory variable " + name + ": " + value)
if (separator == ":?") {
if (environment == null) throw new MissingEnvironmentVariableException("Missing mandatory variable " + name + ": " + value)
if (environment.isEmpty) throw new MissingEnvironmentVariableException("Empty mandatory variable " + name + ": " + value)
}
if (separator.startsWith(":")) if (environment == null || environment.isEmpty) return value
else if (environment == null) return value
}
""
}
/**
* Get the value of the environment variable
*
* #param key - the name of the variable
* #return value or null if not set
*/
def getEnv(key: String) = sys.env.getOrElse(key, System.getProperty(key, s"UNKNOWN_ENV_VAR-$key"))
}
The above constructor can be used in YAML Parser as follows:
/**
* Function that will be used to load the YAML file
* #param yamlFilePath - String with YAML path to read
* #return - FasterXML JsonNode
*/
def parseYaml(yamlFilePath: String): JsonNode = {
val ios = new FileInputStream(new File(yamlFilePath))
// Parsing the YAML file with SnakeYAML - since Jackson Parser does not have Anchors and reference support
val loaderOptions = new LoaderOptions
loaderOptions.setAllowDuplicateKeys(false)
val yaml = new Yaml(
new EnvScalarConstructor,
new Representer,
new DumperOptions,
loaderOptions
)
val mapper = new ObjectMapper().registerModules(DefaultScalaModule)
val yamlObj = yaml.loadAs(ios, classOf[Any])
// Converting the YAML to Jackson YAML - since it has more flexibility
val jsonString = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(yamlObj)
val jsonObj = mapper.readTree(jsonString)
println(jsonString)
jsonObj
}

Related

How to write scala code not like java code?

During an interview, the tech lead said my scala code was just like java code, but using scala api and he wanted me to improve on that.
I am a 3-year java developer and I began scala coding by following the MOOC on Coursera.
Can anyone tell me what is the problem and how can I improve it, please?
I got the job because of my Java acknowledge but the job is based on scala and the coding style is one thing to fix during the trial period.
object Extraction {
// IntelliJ use .idea/modules as current working directory
val FilePathPre = "../../src/main/resources/"
val UserIdFile = "lookup_user.csv"
val ProductIdFile = "lookup_product.csv"
val RatingFile = "agg_ratings.csv"
def readFile(file: String): Iterator[((String, String), String, String)] = {
val Splitter = ","
Source.fromInputStream(this.getClass.getResourceAsStream(file)).getLines()
.map(_.split(Splitter))
.filter(_.size >= 4) // in case line is not valid
.map(x => ((x(0), x(1)), x(2), x(3))) // (userId, ItemId), rating, time
}
def filePrinter(fileName: String, lines: mutable.Map[String, Int]) = {
val file = new File(fileName)
val bw = new BufferedWriter(new FileWriter(file))
lines.toArray.sortWith((a, b) => a._2 < b._2)
.map(x => x._1 + "," + x._2.toString + "\n")
.foreach(bw.write)
bw.close()
}
def aggFilePrinter(fileName: String, lines: mutable.Map[(Int, Int), Float]) = {
val file = new File(fileName)
val bw = new BufferedWriter(new FileWriter(file))
lines.foreach(x => {
val line = x._1._1.toString + "," + x._1._2.toString + "," + (math.round(x._2 * 100.0) / 100.0).toFloat + "\n"
bw.write(line)
})
bw.close()
}
/**
* * une pénalité multiplicative de 0.95 est appliquée au rating
* pour chaque jour d'écart avec le timestamp maximal de input.csv
*
* #param nowTime maximal timestamp at input.csv
* #param pastTime current rating time
* #param rating original rating
* #return final rating multiplied by 0.95 for every day interval from the maximal timestamp
*/
def finalRating(nowTime: String, pastTime: String, rating: String): Float = {
val now =
LocalDateTime.ofInstant(Instant.ofEpochMilli(nowTime.toLong), ZoneId.systemDefault())
val past =
LocalDateTime.ofInstant(Instant.ofEpochMilli(pastTime.toLong), ZoneId.systemDefault())
val diff = ChronoUnit.DAYS.between(past, now)
(math.pow(0.95, diff) * rating.toFloat).toFloat
}
/**
*
* #param file file to extract
*/
def fileDispatcher(file: String) = {
/**
* get idIndice or increment to idIndice and put it to id map
* #param id id in String
* #param idIndice id in Int
* #param idMap userIdMap or productIdMap
* #return (indice for id, max idIndice)
*/
def getIndice(id: String, idIndice: Int, idMap: mutable.Map[String, Int]): (Int, Int) = {
idMap.get(id) match {
case Some(i) => (i, idIndice)
case None => {
val indice = idIndice + 1
idMap += (id -> indice)
(indice, indice)
}
}
}
// 1. scan the file the find the max time
val maxTime = readFile(file).reduce((a, b) => if(a._3 > b._3) a else b)._3
// 2. apply rating condition, calculate rating and return only valid rating lines
val validLines = readFile(file).map(x => (x._1, finalRating(maxTime.toString, x._3, x._2))).filter(_._2 > 0.01)
// 3. loop file lines, sum ratings by (userId, productId), and combine id_String and id_Int
val userIdMap = mutable.Map[String, Int]() // (userId, userIdAsInt)
val productIdMap = mutable.Map[String, Int]() // (productId, productIdAsInt)
val userProductRatingMap = mutable.Map[(Int, Int), Float]() // (userIdAsInt, productIdAsInt, ratingSum)
var userIdIndice = -1
var productIdIndice = -1
validLines.foreach(x => {
val userIdString = x._1._1
val userId = getIndice(userIdString, userIdIndice, userIdMap)
userIdIndice = userId._2
val productIdString = x._1._2
val productId = getIndice(productIdString, productIdIndice, productIdMap)
productIdIndice = productId._2
val key = (userId._1, productId._1)
userProductRatingMap.get(key) match {
case Some(i) => userProductRatingMap += (key -> (i + x._2))
case None => userProductRatingMap += (key -> x._2)
}
})
filePrinter(FilePathPre + UserIdFile, userIdMap)
filePrinter(FilePathPre + ProductIdFile, productIdMap)
aggFilePrinter(FilePathPre + RatingFile, userProductRatingMap)
}
}
Apart of javish code you have also code style issues, suggest to read https://docs.scala-lang.org/style/ at the start (this is not an ultimate guide, but for start is ok). Avoid to use ._1 on tuples, use match { case (a, b, c) => ... } instead.
The main issue is that you use mutable structures, so in scala every structure is immutable by default and it should stay like that unless you have a strong reason to have it mutable. It is more about functional programming which from one perspective tries to avoid mutability and side effects, you can google for this topic more.
So remove mutable. from your code and replace foreach with eg. foldLeft to get newly created immutable.Map on each iteration instead of modifying existing one.

Issue handling CSV values in query string using Swagger generated client and server classes

In one of my APIs Swagger specification I've created a CSV array parameter like this:
...
- name: formats
in: query
description: The format(s) in which the generated report should be returned.
type: array
collectionFormat: csv
items:
type: string
enum:
- pdf
- png
...
Then via 'swagger-codegen' I generated the server classes with -l 'jaxrs', and the client classes with -l 'java'.
The problem I'm having is that my client classes are creating the HTTP request like this:
http://.....:.../.../?...&formats=value1,value2&...
And when the request is handled by my server classes I'm getting an array with a single String with value 'value1,value2'
If my client classes created the HTTP request like this:
http://.....:.../.../?...&formats=value1&formats=value2&....
Then my server classes would correctly instance an array with a 2 values 'value1' and 'value2'
In my generated client classes the function that is generating the query string value is like this:
/**
* Format to {#code Pair} objects.
*
* #param collectionFormat collection format (e.g. csv, tsv)
* #param name Name
* #param value Value
* #return A list of Pair objects
*/
public List<Pair> parameterToPairs(String collectionFormat, String name, Object value){
List<Pair> params = new ArrayList<Pair>();
// preconditions
if (name == null || name.isEmpty() || value == null) return params;
Collection valueCollection = null;
if (value instanceof Collection) {
valueCollection = (Collection) value;
} else {
params.add(new Pair(name, parameterToString(value)));
return params;
}
if (valueCollection.isEmpty()){
return params;
}
// get the collection format
collectionFormat = (collectionFormat == null || collectionFormat.isEmpty() ? "csv" : collectionFormat); // default: csv
// create the params based on the collection format
if (collectionFormat.equals("multi")) {
for (Object item : valueCollection) {
params.add(new Pair(name, parameterToString(item)));
}
return params;
}
String delimiter = ",";
if (collectionFormat.equals("csv")) {
delimiter = ",";
} else if (collectionFormat.equals("ssv")) {
delimiter = " ";
} else if (collectionFormat.equals("tsv")) {
delimiter = "\t";
} else if (collectionFormat.equals("pipes")) {
delimiter = "|";
}
StringBuilder sb = new StringBuilder() ;
for (Object item : valueCollection) {
sb.append(delimiter);
sb.append(parameterToString(item));
}
params.add(new Pair(name, sb.substring(1)));
return params;
}
Maybe the problem isn't in the generated classes but either in:
In my web server's configuration. I'm using 'Wildfly 10.x'
In my Jersey configuration in 'web.xml'
Any ideas?

Convert JSON file into RDF format using Java

Here I want to convert the JSON file into RDF. Here is the JSON
{
"glossary": {
"title": "example glossary",
"GlossDiv": {
"title": "S",
"GlossList": {
"GlossEntry": {
"ID": "SGML",
"SortAs": "SGML",
"GlossTerm": "Standard Generalized Markup Language",
"Acronym": "SGML",
"Abbrev": "ISO 8879:1986",
"GlossDef": {
"para": "A meta-markup language, used to create markup languages such as DocBook.",
"GlossSeeAlso": ["GML", "XML"]
},
"GlossSee": "markup"
}
}
}
}
}
I could not find a proper way to convert it into RDF.
There is no standard way to interpret JSON as RDF. There are several ways you can generate RDF from a JSON file though (in Java or otherwise). You could simply use a JSON parser implemented in Java, then extract the relevant parts and build an RDF graph using a Java library for RDF such as Apache Jena or RDF4J (formerly known as Sesame). However, there are other ways that could make the task much easier:
Transform the JSON file into a JSON-LD file by adding a #context to it. This works well for simple cases but is not sufficient to cover many relevant cases.
Use RML, a language for expressing mappings from various data formats (including JSON) to RDF. It has a reference implementation in Java. RML is an extension of R2RML, so it can also map relational data to RDF, and if you are familiar with R2RML, it is relatively easy to understand how RML works. There is also a graphical editor, but it seems it is not available for download.
Use SPARQL-Generate, a language for expressing mappings from non-RDF data sources (including JSON) to RDF. It has a reference implementation based on Jena. It extends SPARQL, so if you are familiar with SPARQL, it should be quite easy to use it. It can be tested online.
Disclaimer: I contributed to SPARQL-Generate.
If your aim is to simply get valid RDF without making any decisions about structure, you could trivially add a #context object and turn the existing JSON into JSON-LD, e.g.
{
"#context": {"#vocab": "http://example.org/ontology#"},
"glossary": {
"title": "example glossary",
"GlossDiv": {
"title": "S",
"GlossList": {
"GlossEntry": {
"ID": "SGML",
"SortAs": "SGML",
"GlossTerm": "Standard Generalized Markup Language",
"Acronym": "SGML",
"Abbrev": "ISO 8879:1986",
"GlossDef": {
"para": "A meta-markup language, used to create markup languages such as DocBook.",
"GlossSeeAlso": ["GML", "XML"]
},
"GlossSee": "markup"
}
}
}
}
}
This can then be interpreted as RDF using an appropriate RDF/JSON-LD library, the RDF structure would be:
#prefix ns0: <http://example.org/ontology#> .
[] ns0:glossary [
ns0:GlossDiv [
ns0:GlossList [ ns0:GlossEntry [
ns0:Abbrev "ISO 8879:1986" ;
ns0:Acronym "SGML" ;
ns0:GlossDef [
ns0:GlossSeeAlso "GML", "XML" ;
ns0:para "A meta-markup language, used to create markup languages such as DocBook."
] ;
ns0:GlossSee "markup" ;
ns0:GlossTerm "Standard Generalized Markup Language" ;
ns0:ID "SGML" ;
ns0:SortAs "SGML"
] ] ;
ns0:title "S"
] ;
ns0:title "example glossary"
] .
This is perhaps strange RDF, but it can be loaded and manipulated using RDF tools.
You can play with the example in the json-ld playground
You can convert the JSON file into RDF using RML.
To achieve that, you need to create some mapping rules first.
These mapping rules are used by an RML processor to transform your input data
into RDF. An example of an RML processor is the
RML mapper.
Create an RML LogicalSource.
This describes how and where the RML processor should access the input data:
<#LogicalSource>
a rml:logicalSource;
rml:source "data.json";
rml:referenceFormulation ql:JSONPath;
rml:iterator "$.glossary".
This RDF snippet tells an RML processor that he has to retrieve data.json and
iterate over the data with a JSONPath expression (ql:JSONPath) which is
specified by rml:iterator.
Create a TriplesMap. This provides
an RML processor information about how the input data should be transformed into
RDF. An example is given for the provided data:
<#GlossaryMapping>
a rr:TriplesMap;
rml:logicalSource <#LogicalSource>;
rr:subjectMap [
rr:template "http://example.org/glossary/{title}";
];
rr:predicateObjectMap [
rr:predicate ex:title;
rr:objectMap [
rml:reference "title";
];
].
The TriplesMap uses the created LogicalSource to access the data. The TriplesMap
will make sure that an RML processor creates triples with
http://example.org/glossary/{title} as subject, ex:title as predicate and
the value of the JSON property title as object
TriplesMaps can also be linked with each other.
If you have an entry of a list (GlossEntry) to a higher element glossary for
example, you can write the following mapping rules:
<#GlossaryMapping>
a rr:TriplesMap;
rml:logicalSource <#LogicalSource>;
rr:subjectMap [
rr:template "http://example.org/glossary/{title}";
];
rr:predicateObjectMap [
rr:predicate ex:glossDiv;
rr:objectMap [
rr:parentTriplesMap <#GlossListEntryMapping>;
rr:child "GlossDiv.title";
rr:parent "GlossDiv.title";
];
].
<#GlossListEntryMapping>
a rr:TriplesMap;
rml:logicalSource <#LogicalSource>;
rr:subjectMap [
rr:template "http://example.org/entry/{GlossDiv.title}";
];
Both TriplesMaps will be joined with each other taking into account the provided
rr:joinCondition. If no join condition is provided, every subject of the child
will be joined with every subject of the parent.
Mapping rules
I left out some JSON properties in this full example to keep it concise.
#base <http://example.org> .
#prefix rr: <http://www.w3.org/ns/r2rml#> .
#prefix rml: <http://semweb.mmlab.be/ns/rml#> .
#prefix ql: <http://semweb.mmlab.be/ns/ql#> .
#prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
#prefix ex: <http://example.org/ns#> .
<#LogicalSource>
a rml:logicalSource;
rml:source "data.json";
rml:referenceFormulation ql:JSONPath;
rml:iterator "$.glossary".
<#GlossaryMapping>
a rr:TriplesMap;
rml:logicalSource <#LogicalSource>;
rr:subjectMap [
rr:template "http://example.org/glossary/{title}";
];
rr:predicateObjectMap [
rr:predicate ex:title;
rr:objectMap [
rml:reference "title";
];
];
rr:predicateObjectMap [
rr:predicate ex:glossDiv;
rr:objectMap [
rr:parentTriplesMap <#GlossListEntryMapping>;
rr:child "GlossDiv.title";
rr:parent "GlossDiv.title";
];
].
<#GlossListEntryMapping>
a rr:TriplesMap;
rml:logicalSource <#LogicalSource>;
rr:subjectMap [
rr:template "http://example.org/entry/{GlossDiv.title}";
];
rr:predicateObjectMap [
rr:predicate ex:ID;
rr:objectMap [
rml:reference "GlossDiv.GlossList.GlossEntry.ID"
];
];
rr:predicateObjectMap [
rr:predicate ex:Abbrev;
rr:objectMap [
rml:reference "GlossDiv.GlossList.GlossEntry.Abbrev"
];
];
# Other properties can be mapped too if needed
rr:predicateObjectMap [
rr:predicate ex:glossSeeAlso;
rr:objectMap [ # Mapping arrays can also be done
rml:reference "GlossDiv.GlossList.GlossEntry.GlossDef.GlossSeeAlso.[*]";
];
].
Output
<http://example.org/entry/S> <http://example.org/ns#glossSeeAlso> "GML".
<http://example.org/entry/S> <http://example.org/ns#glossSeeAlso> "XML".
<http://example.org/entry/S> <http://example.org/ns#ID> "SGML".
<http://example.org/entry/S> <http://example.org/ns#Abbrev> "ISO 8879:1986".
<http://example.org/glossary/example%20glossary> <http://example.org/ns#title> "example glossary".
<http://example.org/glossary/example%20glossary> <http://example.org/ns#glossDiv> <http://example.org/entry/S>.
Note: I contribute to RML and its technologies.
If I understand your question correctly, I recommend using Apache Jena, Apache's open source semantic web library.
Here is an RDF creation tutorial.
I've also found JSON2RDF, a project that claims to utilize Jena in creating a declerative language for converting JSONs into RDFs, but I have not tested it myself and the docs are vague. Please share your experience if you do end up looking into it.
We have released open-source JSON2RDF converter (not to be confused with the JSON2RDF mentioned by vaiden) that allows you to do exactly that:
https://github.com/AtomGraph/JSON2RDF
JSON2RDF + SPARQL can be used as a more flexible alternative to JSON-LD + #context.
We're providing a JSON to JSONLD converting service: https://json2ld.mapper.tokyo/
First, you paste or upload your JSON data, then it instantly generates their JSONLD version.
It automatically maps some existing vocabularies such as schema.org if there's a matching term in the JSON data such as "name", which is mapped to https://schema.org/name.
You can edit which vocabularies or terms are mapped and publish your context from your GitHub repo.
I spent some time recently to paste some older java code together to create a command line tool named 'oi'.
https://github.com/jschnasse/oi
Call
oi stack44753298.json -t turtle
Prints
_:b0 <info:oi/glossary> _:b1 .
_:b1 <info:oi/GlossDiv> _:b2 .
_:b2 <info:oi/GlossList> _:b3 .
_:b3 <info:oi/GlossEntry> _:b4 .
_:b4 <info:oi/Abbrev> "ISO 8879:1986";
<info:oi/Acronym> "SGML";
<info:oi/GlossDef> _:b5 .
_:b5 <info:oi/GlossSeeAlso> "GML", "XML";
<info:oi/para> "A meta-markup language, used to create markup languages such as DocBook." .
_:b4 <info:oi/GlossSee> "markup";
<info:oi/GlossTerm> "Standard Generalized Markup Language";
<info:oi/ID> "SGML";
<info:oi/SortAs> "SGML" .
_:b2 <info:oi/title> "S" .
_:b1 <info:oi/title> "example glossary" .
The tool uses a very rough approach to transform rdf to json and vice versa. For the json to rdf case it creates an adhoc context just by putting all json keys into an info:namespace. After that it uses the context to read in the json as jsonld. Exactly like already described in this thread by brinxmat.
It follows roughly the ideas I wrote up in recent stackoverflow threads:
How do I add a start and end time to a RDF triple?
How to convert RDF to pretty nested JSON using java rdf4j
Read JSON with this JSON parser to Java objects and create rdf with Java objects which is previously read.
package your_package;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.nio.charset.Charset;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
<html>
/**
* JSON is an open standard format that uses human-readable text to transmit
* Data objects consisting of attribute–value pairs. It is used primarily to
* Transmit data between a server and web application, as an alternative to XML.
*
* JSON's basic types are:
* <ul>
* <li><b>Number</b>: a signed decimal number that may contain a fractional part
* And may use exponential E notation. JSON does not allow non-numbers like NaN,
* Nor does it make any distinction between integer and floating-point. (Even
* Though JavaScript uses a double-precision floating-point format for all its
* Numeric values, other languages implementing JSON may encode numbers
* differently) </li>
* <li><b>String</b>: a sequence of zero or more Unicode characters. Strings are
* Delimited with double-quotation marks and support a backslash escaping
* syntax.</li>
* <li><b>Boolean</b>: either of the values {#code true} or {#code false}</li>
* <li><b>Array</b>: an ordered list of zero or more values, each of which may
* Be of any type. Arrays use square bracket notation with elements being
* comma-separated.</li>
* <li><b>Object</b>: an unordered collection of name/value pairs where the
* Names (also called keys) are strings. Since objects are intended to represent
* Associative arrays, it is recommended, though not required, that each key is
* Unique within an object. Objects are delimited with curly brackets and use
* Commas to separate each pair, while within each pair the colon {#code ':'}
* Character separates the key or name from its value.</li>
* <li><b>null</b>: An empty value, using the word null</li>
* </ul>
* Conversion table:
* <table border='1'>
* <tr><td>java.util.Map</td><td>JSON Object {"key":[0,2,3]}</td></tr>
* <tr><td>java.util.Collection</td><td>JSON Array [0,1,2,"string"]</td></tr>
* <tr><td>java.lang.String</td><td>JSON String "example"</td></tr>
* <tr><td>java.lang.Boolean</td><td>JSON Boolean (true/false) </td></tr>
* <tr><td>java.lang.Number</td><td>JSON Number (-2.5E2) </td></tr>
* <tr><td>null</td><td>JSON Null (null) </td></tr>
* </table>
* Any other object will be write with the next formula:
* <u>{#code obj.toString()}</u>. For example:
* {#Code write (out, new java.lang.object())}. The output stream will be
* Contains the hashcode of newly created object because the default object
* toString() method returns the object hashcode. This class supported the
* Duplicated keys in the object map.
*
* The JavaScript Object Notation (JSON) Data Interchange Format:
* <A href='http://tools.ietf.org/html/rfc7159'>RFC-7159</a>
*
* #See DuplicatedKeyList
*/
public class Json {
/**
* This field represents when the json object is finished, no more available
* data to processing.
*/
private static final Object close = new Object();
/**
* The step offsets in the rows.
*/
private static final int writeSpaceCount = 1;
/**
* 'n' 'u' 'l' 'l'
*/
public static final byte[] NULL = new byte[]{'n', 'u', 'l', 'l'};
/**
* The null identifer, also called: the first character of null.
*/
public static final int NULL_LOWER = 'n';
/**
* The null identifer, also called: the first character of null (uppercase).
*/
public static final int NULL_UPPER = 'N';
/**
* The first character of {#code true}.
*/
public static final int TRUE = 't';
/**
* The first character of {#code true} (uppercase).
*/
public static final int TRUE_UPPER = 'T';
/**
* The first character of {#code false}.
*/
public static final int FALSE = 'f';
/**
* The first character of {#code false} (uppercase).
*/
public static final int FALSE_UPPER = 'F';
/**
* Colon ascii value
*/
public static final int COLON = ':';
/**
* Comma ascii value
*/
public static final int COMMA = ',';
/**
* left bracket (the list identifier, first character)
*/
public static final int LEFT_BRACKET = '[';
/**
* left brace (the map identifier, first character)
*/
public static final int LEFT_BRACE = '{';
/**
* right bracket (the list identifier, last character)
*/
public static final int RIGHT_BRACKET = ']';
/**
* right bracket (the map identifier, last character)
*/
public static final int RIGHT_BRACE = '}';
/**
* the string identifier: {#code "}
*/
public static final int STRING = '"';
/**
* the space ascii value
*/
public static final int SP = ' ';
/**
* the backslash ascii value
*/
public static final int BS = '\\';
/**
* the CR (carriage return) ascii value
*/
public static final int CR = 13;
/**
* the line feed ascii value
*/
public static final int LF = 10;
/**
* the end of file identifier
*/
public static final int EOF = -1;
/**
* end of line identifier (CR + LF)
*/
public static final byte[] EOL = new byte[]{CR, LF};
/**
* the byte array buffer to read
*/
private final ByteArrayOutputStream buf = new ByteArrayOutputStream();
/**
* Creates a new JSON which can read and write json objects.
*/
public Json() {
}
/**
* Creates a new empty map.
*
* #return a new empty map.
*/
private Map createEmptyMap() {
return new HashMap();
}
/**
* Writes a specified object {#code obj} to a specified output stream
* {#code out}, also called: creates a json document from the specified
* object. Duplicated keys are supported. Conversion table: see JSON class
* javadoc.
*
* #param out the specified output stream
* #param obj the specified object
* #throws IOException If IO Error Occurs.
* #see Json
*/
public void write(PrintStream out, Object obj) throws IOException {
write(out, obj, false);
}
/**
* Writes a specified object {#code obj} to a specified print stream
* {#code out}, also called: creates a json document from the specified
* object. Duplicated keys are supported. Conversion table: see JSON class
* javadoc.
*
* #param out the specified print stream
* #param obj the specified object
* #param format {#code true} if the output is formatted, otherwise
* {#code false}.
* #throws IOException If IO Error Occurs.
* #see Json
* #see DuplicatedKeyList
*/
public void write(PrintStream out, Object obj, boolean format) throws IOException {
write(out, obj, format, 0);
}
/**
* Writes a specified object {#code obj} to a specified print stream
* {#code out}, also called: creates a json document from the specified
* object. Duplicated keys are supported. Conversion table: see JSON class
* javadoc.
*
* #param out the specified print stream
* #param obj the specified object
* #param format {#code true} if the output is formatted, otherwise
* {#code false}.
* #param charset the charset which represents the json document encodings
* #param depth the current depth from the root element
* #throws IOException If IO Error Occurs.
* #see Json
* #see DuplicatedKeyList
*/
private synchronized void write(PrintStream out, Object obj, boolean format, int depth) throws IOException {
if (obj == null) {
out.write(NULL);
out.flush();
return;
}
if (obj instanceof String) {
out.write(STRING);
out.print(escape((String) obj));
out.write(STRING);
out.flush();
return;
} else if (obj instanceof Map) {
out.write(LEFT_BRACE);
Map map = (Map) obj;
Iterator<Map.Entry> it = map.entrySet().iterator();
Map.Entry entry;
while (it.hasNext()) {
entry = it.next();
String key = escape(entry.getKey().toString());
Object val = entry.getValue();
if (val instanceof DuplicatedKeyList) {
writeMulti(out, key, (List) val, format, depth);
} else {
if (format) {
writeBreak(out, depth + writeSpaceCount);
}
write(out, key, format, depth + writeSpaceCount);
out.write(COLON);
if (format) {
writeSpace(out, writeSpaceCount);
}
write(out, val, format, depth + writeSpaceCount);
}
if (it.hasNext()) {
out.write(COMMA);
}
}
if (format) {
writeBreak(out, depth);
}
out.write(RIGHT_BRACE);
out.flush();
return;
} else if (obj instanceof Collection) {
out.write(LEFT_BRACKET);
Iterator it = ((Collection) obj).iterator();
while (it.hasNext()) {
if (format) {
writeBreak(out, depth + writeSpaceCount);
}
write(out, it.next(), format, depth + writeSpaceCount);
if (it.hasNext()) {
out.write(COMMA);
}
}
if (format) {
writeBreak(out, depth);
}
out.write(RIGHT_BRACKET);
out.flush();
return;
}
if (obj instanceof Number || obj instanceof Boolean) {
out.print(obj);
} else {
out.write(STRING);
out.print(escape(obj.toString()));
out.write(STRING);
}
out.flush();
}
/**
* Reads a specified input stream {#code in} which contains json elements
* and returns the java object representation of json elements. Conversion
* table: see JSON class javadoc.
*
* #param in the specified input stream
* #return the java object representation of json elements.
* #throws IOException If IO Error Occurs.
* #see Json
* #see DuplicatedKeyList
*/
public Object read(InputStream in) throws IOException {
return read(in, Charset.forName("UTF-8"));
}
/**
* Reads a specified input stream {#code in} which contains json elements
* and returns the java object representation of json elements. Conversion
* table: see JSON class javadoc.
*
* #param in the specified input stream
* #param charset the json document encodings
* #return the java object representation of json elements.
* #throws IOException If IO Error Occurs.
* #see Json
* #see DuplicatedKeyList
*/
public synchronized Object read(InputStream in, Charset charset) throws IOException {
int b;
while ((b = in.read()) != EOF) {
if (b > 32 && b != COMMA) {
switch (b) {
//list
case LEFT_BRACKET: {
List list = new ArrayList();
Object obj;
while ((obj = read(in, charset)) != close) {
if (obj instanceof Finish) {
list.add(((Finish) obj).val);
break;
} else {
list.add(obj);
}
}
return list;
}
//map
case LEFT_BRACE: {
Map map = createEmptyMap();
Object key;
Object val;
while ((key = read(in, charset)) != close) {
while ((b = in.read()) != COLON) {
if (b == EOF) {
throw new IOException("EOF");
}
}
val = read(in, charset);
if (map.containsKey(key)) {
Object prev = map.get(key);
DuplicatedKeyList list;
if (prev instanceof DuplicatedKeyList) {
list = (DuplicatedKeyList) prev;
//((DuplicatedKeyList) prev).add(val);
} else {
list = new DuplicatedKeyList(new ArrayList());
list.add(prev);
}
list.add(val);
map.put(key, list);
//}
System.err.println("WARNING: duplicated key: " + key);
} else {
if (val instanceof Finish) {
val = ((Finish) val).val;
map.put(key, val);
break;
} else {
map.put(key, val);
}
}
}
return map;
}
//string
case STRING: {
buf.reset();
int a = 0;
while ((b = in.read()) != STRING || a == BS) {
buf.write(b);
a = b;
}
return unescape(buf.toString(charset.name()));
}
case TRUE_UPPER: {
}
//true
case TRUE: {
in.skip(4);
return true;
}
//false
case FALSE_UPPER: {
}
case FALSE: {
in.skip(5);
return false;
}
//null
case NULL_UPPER: {
}
case NULL_LOWER: {
in.skip(4);
return null;
}
//map right brackets
case RIGHT_BRACE: {
}
case RIGHT_BRACKET: {
return close;
}
//number
default: {
buf.reset();
buf.write(b);
while ((b = in.read()) != EOF) {
if (isRegular(b)) {
buf.write(b);
} else {
break;
}
}
String str = buf.toString(charset.name());
Number num;
if (str.indexOf('.') != -1) {
num = Double.valueOf(str);
} else {
num = Long.valueOf(str);
}
if (b == RIGHT_BRACKET || b == RIGHT_BRACE) {
return new Finish(num);
}
return num;
}
}
}
}
return close;
}
private void writeMulti(PrintStream out, Object key, Collection value, boolean format, int depth) throws IOException {
Iterator it = value.iterator();
while (it.hasNext()) {
if (format) {
writeBreak(out, depth + writeSpaceCount);
}
write(out, key, format, depth + writeSpaceCount);
out.write(COLON);
if (format) {
writeSpace(out, writeSpaceCount);
}
write(out, it.next(), format, depth + writeSpaceCount);
if (it.hasNext()) {
out.write(COMMA);
}
}
}
/**
* Returns {#code true} if the specified {#code b} byte is regular
* character, otherwise {#code false}.
*
* #param b the specified byte
* #return {#code true} if the specified {#code b} byte is regular
* character, otherwise {#code false}.
*/
private boolean isRegular(int b) {
return b > 32
&& b != LEFT_BRACKET
&& b != LEFT_BRACE
&& b != COMMA
&& b != RIGHT_BRACKET
&& b != RIGHT_BRACE;
}
/**
* Returns the unescaped string.
*
* #param str the input source
* #return the unescaped string.
*/
private String unescape(String str) {
str = str.replace("\\b", "\b");
str = str.replace("\\f", "\f");
str = str.replace("\\n", "\n");
str = str.replace("\\r", "\r");
str = str.replace("\\t", "\t");
str = str.replace("\\\"", "\"");
return str;
}
/**
* Returns the escaped string.
*
* #param str the input source
* #return the escaped string.
*/
public static String escape(String str) {
str = str.replace("\b", "\\b");
str = str.replace("\f", "\\f");
str = str.replace("\n", "\\n");
str = str.replace("\r", "\\r");
str = str.replace("\t", "\\t");
str = str.replace("\"", "\\\"");
return str;
}
/**
* Writes spaces to a specified output {#code out}.
*
* #param out the specified output
* #param spaceCount the spaces count
* #throws IOException if IO Error Occurs.
*/
private void writeSpace(OutputStream out, int spaceCount) throws IOException {
byte[] b = new byte[spaceCount];
for (int i = 0; i < b.length; i++) {
b[i] = SP;
}
out.write(b);
}
/**
* Writes break line and spaces to a specified output {#code out}.
*
* #param out the specified output
* #param spaceCount the spaces count
* #throws IOException if IO Error Occurs.
*/
private void writeBreak(OutputStream out, int spaceCount) throws IOException {
out.write(EOL);
writeSpace(out, spaceCount);
}
/**
* Creates a new instance of JSON.
*
* #return a new instance of JSON.
*/
public static Json getInstance() {
return new Json();
}
public Object read(byte[] b) throws IOException {
return read(new ByteArrayInputStream(b));
}
/**
* This class can contains json elements to one key. json objects (map) can
* contains duplicate values to one key.
*/
public static class DuplicatedKeyList extends AbstractList {
private List list;
public DuplicatedKeyList(List list) {
if (list == null) {
throw new NullPointerException("list is null");
}
this.list = list;
}
#Override
public void add(int index, Object element) {
list.add(index, element);
}
#Override
public Object set(int index, Object element) {
return list.set(index, element);
}
#Override
public Object remove(int index) {
return list.remove(index);
}
#Override
public Object get(int index) {
return list.get(index);
}
#Override
public int size() {
return list.size();
}
#Override
public String toString() {
Iterator it = iterator();
if (!it.hasNext()) {
return "[]";
}
StringBuilder sb = new StringBuilder();
sb.append('[');
for (;;) {
sb.append('#').append('=');
Object e = it.next();
sb.append(e == this ? "(this Collection)" : e);
if (!it.hasNext()) {
return sb.append(']').toString();
}
sb.append(',').append(' ');
}
}
}
/**
* This class is a marker class which must use if the json collection (map
* or list) will be finished.
*/
class Finish {
/**
* the last object
*/
private Object val;
/**
* Creates a new instance with the specified {#code val} value.
*
* #param val the specified value
*/
public Finish(Object val) {
this.val = val;
}
}
}

Parse Accept-Language header in Java

The accept-language header in request is usually a long complex string -
Eg.
Accept-Language : en-ca,en;q=0.8,en-us;q=0.6,de-de;q=0.4,de;q=0.2
Is there a simple way to parse it in java? Or a API to help me do that?
I would suggest using ServletRequest.getLocales() to let the container parse Accept-Language rather than trying to manage the complexity yourself.
For the record, now it is possible with Java 8:
Locale.LanguageRange.parse()
Here's an alternative way to parse the Accept-Language header which doesn't require a servlet container:
String header = "en-ca,en;q=0.8,en-us;q=0.6,de-de;q=0.4,de;q=0.2";
for (String str : header.split(",")){
String[] arr = str.trim().replace("-", "_").split(";");
//Parse the locale
Locale locale = null;
String[] l = arr[0].split("_");
switch(l.length){
case 2: locale = new Locale(l[0], l[1]); break;
case 3: locale = new Locale(l[0], l[1], l[2]); break;
default: locale = new Locale(l[0]); break;
}
//Parse the q-value
Double q = 1.0D;
for (String s : arr){
s = s.trim();
if (s.startsWith("q=")){
q = Double.parseDouble(s.substring(2).trim());
break;
}
}
//Print the Locale and associated q-value
System.out.println(q + " - " + arr[0] + "\t " + locale.getDisplayLanguage());
}
You can find an explanation of the Accept-Language header and associated q-values here:
http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
Many thanks to Karl Knechtel and Mike Samuel. Thier comments to the original question helped point me in the right direction.
We are using Spring boot and Java 8. This works
In ApplicationConfig.java write this
#Bean
public LocaleResolver localeResolver() {
return new SmartLocaleResolver();
}
and I have this list in my constants class that has languages that we support
List<Locale> locales = Arrays.asList(new Locale("en"),
new Locale("es"),
new Locale("fr"),
new Locale("es", "MX"),
new Locale("zh"),
new Locale("ja"));
and write the logic in the below class.
public class SmartLocaleResolver extends AcceptHeaderLocaleResolver {
#Override
public Locale resolveLocale(HttpServletRequest request) {
if (StringUtils.isBlank(request.getHeader("Accept-Language"))) {
return Locale.getDefault();
}
List<Locale.LanguageRange> ranges = Locale.LanguageRange.parse("da,es-MX;q=0.8");
Locale locale = Locale.lookup(ranges, locales);
return locale ;
}
}
ServletRequest.getLocale() is certainly the best option if it is available and not overwritten as some frameworks do.
For all other cases Java 8 offers Locale.LanguageRange.parse() as previously mentioned by Quiang Li. This however only gives back a Language String, not a Locale. To parse the language strings you can use Locale.forLanguageTag() (available since Java 7):
final List<Locale> acceptedLocales = new ArrayList<>();
final String userLocale = request.getHeader("Accept-Language");
if (userLocale != null) {
final List<LanguageRange> ranges = Locale.LanguageRange.parse(userLocale);
if (ranges != null) {
ranges.forEach(languageRange -> {
final String localeString = languageRange.getRange();
final Locale locale = Locale.forLanguageTag(localeString);
acceptedLocales.add(locale);
});
}
}
return acceptedLocales;
Locale.forLanguageTag("en-ca,en;q=0.8,en-us;q=0.6,de-de;q=0.4,de;q=0.2")
The above solutions lack some kind of validation. Using ServletRequest.getLocale() returns the server locale if the user does not provides a valid one.
Our websites lately received spam requests with various Accept-Language heades like:
secret.google.com
o-o-8-o-o.com search shell is much better than google!
Google officially recommends o-o-8-o-o.com search shell!
Vitaly rules google ☆*:。゜゚・*ヽ(^ᴗ^)ノ*・゜゚。:*☆ ¯\_(ツ)_/¯(ಠ益ಠ)(ಥ‿ಥ)(ʘ‿ʘ)ლ(ಠ_ಠლ)( ͡° ͜ʖ ͡°)ヽ(゚Д゚)ノʕ•̫͡•ʔᶘ ᵒᴥᵒᶅ(=^ ^=)oO
This implementation can optional check against a supported list of valid Locale. Without this check a simple request with "test" or (2, 3, 4) still bypass the syntax-only validation of LanguageRange.parse(String).
It optional allows empty and null values to allow search engine crawler.
Servlet Filter
final String headerAcceptLanguage = request.getHeader("Accept-Language");
// check valid
if (!HttpHeaderUtils.isHeaderAcceptLanguageValid(headerAcceptLanguage, true, Locale.getAvailableLocales()))
return;
Utility
/**
* Checks if the given accept-language request header can be parsed.<br>
* <br>
* Optional the parsed LanguageRange's can be checked against the provided
* <code>locales</code> so that at least one locale must match.
*
* #see LanguageRange#parse(String)
*
* #param acceptLanguage
* #param isBlankValid Set to <code>true</code> if blank values are also
* valid
* #param locales Optional collection of valid Locale to validate any
* against.
*
* #return <code>true</code> if it can be parsed
*/
public static boolean isHeaderAcceptLanguageValid(final String acceptLanguage, final boolean isBlankValid,
final Locale[] locales)
{
// allow null or empty
if (StringUtils.isBlank(acceptLanguage))
return isBlankValid;
try
{
// check syntax
final List<LanguageRange> languageRanges = Locale.LanguageRange.parse(acceptLanguage);
// wrong syntax
if (languageRanges.isEmpty())
return false;
// no valid locale's to check against
if (ArrayUtils.isEmpty(locales))
return true;
// check if any valid locale exists
for (final LanguageRange languageRange : languageRanges)
{
final Locale locale = Locale.forLanguageTag(languageRange.getRange());
// validate available locale
if (ArrayUtils.contains(locales, locale))
return true;
}
return false;
}
catch (final Exception e)
{
return false;
}
}

Apple pList form of XML can I parse it through Android Java? [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 7 years ago.
Improve this question
Can we parse the iPhone/iPad based pList XML from Java on Android?
Please tell me if any such library you have used or know about?
Here is a class I wrote to parse an xml plist file. It uses the XmlPullParser to do the parsing. I have only implemented what I needed for my project. But this should get you started for extending the class if you need more than what this class provides.
File : XMLPropertyListConfiguration.java
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.example.plist;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Stack;
import org.xmlpull.v1.XmlPullParser;
import android.annotation.SuppressLint;
import android.util.Xml;
//import android.util.Log;
/**
* This class will parse a plist xml file and store the contents in a
* hierarchical HashMap of <String, Object> tuples, where the Object value could
* be another HashMap, an ArrayList, Boolean, Date, String or Integer value.
*
* Use the getConfiguration methods to retrieve the values from the parsed plist
* file.
*
* The key names for nested dictionary references must be of the form :
* Dict1KeyName.Dict2KeyName.ElementKeyName
*
* #author akoscz
*
*/
public class XMLPropertyListConfiguration {
// private static final String TAG = "plist";
/**
* The nested (hierarchical) HashMap which holds our key-value pairs of our
* plist file.
*/
protected HashMap<String, Object> mPlistHashMap;
/**
* Constructor. Parse a plist file from the given InputStream.
*
* #param inputStream
* The InputStream which has the bytes of the plist file we need
* to parse.
*/
public XMLPropertyListConfiguration(InputStream inputStream) {
mPlistHashMap = new HashMap<String, Object>();
if (inputStream != null) {
parse(inputStream);
}
}
/**
* Get an String configuration value for the given key.
*
* #param keyName
* The name of the key to look up in the configuration
* dictionary.
* #return The String value of the specified key.
*/
public String getConfiguration(String keyName) {
return (String) getConfigurationObject(keyName);
}
/**
* Get a String configuration value for the given key. If there is no value
* for the given key, then return the default value.
*
* #param keyName
* The name of the key to look up in the configuration
* dictionary.
* #param defaultValue
* The default value to return if they key has no associated
* value.
* #return The String value of the specified key, or defaultValue if the
* value for keyName is null.
*/
public String getConfigurationWithDefault(String keyName, String defaultValue) {
String value = getConfiguration(keyName);
if (value == null) {
return defaultValue;
}
return value;
}
/**
* Get an Integer configuration value for the given key.
*
* #param keyName
* The name of the key to look up in the configuration
* dictionary.
* #return The Integer value of the specified key.
*/
public Integer getConfigurationInteger(String keyName) {
return (Integer) getConfigurationObject(keyName);
}
/**
* Get an Integer configuration value for the given key. If there is no
* value for the given key, then return the default value.
*
* #param keyName
* The name of the key to look up in the configuration
* dictionary.
* #param defaultValue
* The default value to return if they key has no associated
* value.
* #return The Integer value of the specified key, or defaultValue if the
* value for keyName is null.
*/
public Integer getConfigurationIntegerWithDefault(String keyName, Integer defaultValue) {
Integer value = getConfigurationInteger(keyName);
if (value == null) {
return defaultValue;
}
return value;
}
/**
* Get a Date configuration value for the given key.
*
* #param keyName
* The name of the key to look up in the configuration
* dictionary.
* #return The Date value of the specified key.
*/
public Date getConfigurationDate(String keyName) {
return (Date) getConfigurationObject(keyName);
}
/**
* Get a Date configuration value for the given key. If there is no value
* for the given key, then return the default value.
*
* #param keyName
* The name of the key to look up in the configuration
* dictionary.
* #param defaultValue
* The default value to return if they key has no associated
* value.
* #return The Date value of the specified key, or defaultValue if the value
* for keyName is null.
*/
public Date getConfigurationDateWithDefault(String keyName, Date defaultValue) {
Date value = getConfigurationDate(keyName);
if (value == null) {
return defaultValue;
}
return value;
}
/**
* Get a Boolean configuration value for the given key.
*
* #param keyName
* The name of the key to look up in the configuration
* dictionary.
* #return The Boolean value of the specified key.
*/
public Boolean getConfigurationBoolean(String keyName) {
return (Boolean) getConfigurationObject(keyName);
}
/**
* Get a Boolean configuration value for the given key. If there is no
* value for the given key, then return the default value.
*
* #param keyName
* The name of the key to look up in the configuration
* dictionary.
* #param defaultValue
* The default value to return if they key has no associated
* value.
* #return The Boolean value of the specified key, or defaultValue if the
* value for keyName is null.
*/
public Boolean getConfigurationBooleanWithDefault(String keyName,
Boolean defaultValue) {
Boolean value = getConfigurationBoolean(keyName);
if (value == null) {
return defaultValue;
}
return value;
}
/**
* Utility method which uses a XmlPullParser to iterate through the XML
* elements and build up a hierarchical HashMap representing the key-value
* pairs of the plist configuration file.
*
* #param inputStream
* The InputStream which contains the plist XML file.
*/
public void parse(InputStream inputStream) {
mPlistHashMap.clear();
XmlPullParser parser = Xml.newPullParser();
try {
parser.setInput(inputStream, null);
int eventType = parser.getEventType();
int arrayDepth = 0;
boolean done = false;
boolean parsingArray = false;
String name = null;
String key = null;
Stack<HashMap<String, Object>> stack = new Stack<HashMap<String, Object>>();
HashMap<String, Object> dict = null;
ArrayList<Object> array = null;
while (eventType != XmlPullParser.END_DOCUMENT && !done) {
switch (eventType) {
case XmlPullParser.START_DOCUMENT:
// Log.d(TAG, "START_DOCUMENT");
break;
case XmlPullParser.START_TAG:
name = parser.getName();
if (name.equalsIgnoreCase("dict")) {
// root dict element
if (key == null) {
mPlistHashMap.clear();
dict = mPlistHashMap;
} else if (parsingArray) {
// Log.d(TAG, "START_TAG dict : inside array");
HashMap<String, Object> childDict = new HashMap<String, Object>();
array.add(childDict);
stack.push(dict);
dict = childDict;
} else {
// Log.d(TAG, "START_TAG dict : " + key);
HashMap<String, Object> childDict = new HashMap<String, Object>();
dict.put(key, childDict);
stack.push(dict);
dict = childDict;
}
} else if (name.equalsIgnoreCase("key")) {
key = parser.nextText();
} else if (name.equalsIgnoreCase("integer")) {
dict.put(key, Integer.valueOf(parser.nextText()));
} else if (name.equalsIgnoreCase("string")) {
if (parsingArray && (parser.getDepth() == (arrayDepth + 1))) {
array.add(parser.nextText());
} else {
dict.put(key, parser.nextText());
}
} else if (name.equalsIgnoreCase("array")) {
parsingArray = true;
array = new ArrayList<Object>();
dict.put(key, array);
arrayDepth = parser.getDepth();
} else if (name.equalsIgnoreCase("date")) {
dict.put(key, parseDate(parser.nextText()));
} else if (name.equalsIgnoreCase("true")) {
dict.put(key, Boolean.TRUE);
} else if (name.equalsIgnoreCase("false")) {
dict.put(key, Boolean.FALSE);
}
break;
case XmlPullParser.END_TAG:
name = parser.getName();
if (name.equalsIgnoreCase("dict")) {
// Log.d(TAG, "END_TAG dict");
if (!stack.empty()) {
dict = stack.pop();
}
} else if (name.equalsIgnoreCase("array")) {
parsingArray = false;
array = null;
} else if (name.equalsIgnoreCase("plist")) {
done = true;
}
break;
case XmlPullParser.END_DOCUMENT:
// Log.d(TAG, "END_DOCUMENT");
break;
}
eventType = parser.next();
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
/**
* Method to parse an ISO8601 string to a Date object.
* http://www.java2s.com/Code/Java/Data-Type/ISO8601dateparsingutility.htm
*
* #param input
* The ISO8601 date string
* #return The Date object representing the ISO8601 date string.
* #throws java.text.ParseException
*/
#SuppressLint("SimpleDateFormat")
public static Date parseDate(String input) throws java.text.ParseException {
// NOTE: SimpleDateFormat uses GMT[-+]hh:mm for the TZ which breaks
// things a bit. Before we go on we have to repair this.
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz");
// this is zero time so we need to add that TZ indicator for
if (input.endsWith("Z")) {
input = input.substring(0, input.length() - 1) + "GMT-00:00";
} else {
int inset = 6;
String s0 = input.substring(0, input.length() - inset);
String s1 = input.substring(input.length() - inset, input.length());
input = s0 + "GMT" + s1;
}
return df.parse(input);
}
/**
* Utility method which tokenizes the given keyName using the "." delimiter
* and then looks up each token in the configuration dictionary. If the
* token key points to a dictionary then it proceeds to the next token key
* and looks up value of the token key in the dictionary it found from the
* previous token key.
*
* #param keyName
* The fully qualified key name.
* #return The Object value associated with the given key, or null if the
* key does not exist.
*/
#SuppressWarnings("unchecked")
protected Object getConfigurationObject(String keyName) {
String[] tokens = keyName.split("\\.");
if (tokens.length > 1) {
HashMap<String, Object> dict = mPlistHashMap;
Object obj;
for (int i = 0; i < tokens.length; i++) {
obj = dict.get(tokens[i]);
if (obj instanceof HashMap<?, ?>) {
dict = (HashMap<String, Object>) obj;
continue;
}
return obj;
}
}
return mPlistHashMap.get(keyName);
}
}
Following is a convenience class which extends XMLPropertyListConfiguration to encapsulate the logic of parsing your domain specific plist file. The getter methods are simple delegates to the parent class getConfiguration() methods. Note that you can specify default values to be returned if the key is not present in the plist file.
I have also added some debug methods to print out the keys and values to the debug log.
File : ExamplePListParser.java
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.example.plist;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import android.util.Log;
public class ExamplePListParser extends XMLPropertyListConfiguration {
private static final String TAG = "ExamplePListParser";
public ExamplePListParser(InputStream inputStream) {
super(inputStream);
}
public Integer getVersion() {
return getConfigurationIntegerWithDefault("Version", 1);
}
public String getUrl() {
return getConfigurationWithDefault("Url", "http://");
}
public Integer getBrowserVideoWidth(){
return getConfigurationIntegerWithDefault("Browser.VideoWidth", 1280);
}
public Integer getBrowserVideoHeight(){
return getConfigurationIntegerWithDefault("Browser.VideoHeight", 800);
}
public String getRating() {
return getConfigurationWithDefault("Rating", "G");
}
public Date getExpireDate() {
return getConfigurationDateWithDefault("ExpireDate", new Date());
}
public Boolean getHighRes() {
return getConfigurationBooleanWithDefault("HighRes", Boolean.TRUE);
}
/**
* Debug method. Print all the "dict" key names from our plist configuration
* file
*/
public void dumpKeys() {
printHashKeys("root", mPlistHashMap);
}
/**
* Debug method. Iterate through all the methods of this class and print our
* the resulting values.
*/
public void dumpValues() {
try {
Class<? extends XMLPropertyListConfiguration> c = this.getClass();
Method m[] = c.getDeclaredMethods();
for (int i = 0; i < m.length; i++) {
// only invoke getter methods
if (m[i].getName().startsWith("get")) {
// Log.d(TAG, m[i].getName());
if (m[i].getReturnType() == Integer.class) {
Log.d(TAG, m[i].getName() + " --> " + (Integer) m[i].invoke(this, (Object[]) null));
} else if (m[i].getReturnType() == ArrayList.class) {
Log.d(TAG, m[i].getName() + " --> Array");
dumpArrayList((ArrayList<?>) m[i].invoke(this, (Object[]) null));
} else if (m[i].getReturnType() == Date.class) {
Log.d(TAG, m[i].getName() + " --> " + (Date) m[i].invoke(this, (Object[]) null));
} else if (m[i].getReturnType() == Boolean.class) {
Log.d(TAG, m[i].getName() + " --> " + (Boolean) m[i].invoke(this, (Object[]) null));
} else if (m[i].getReturnType() == String.class) {
Log.d(TAG, m[i].getName() + " --> " + (String) m[i].invoke(this, (Object[]) null));
} else {
Log.d(TAG, m[i].getName() + " --> UNSUPPORTED TYPE");
}
}
}
} catch (Throwable e) {
e.printStackTrace();
}
}
private void dumpArrayList(ArrayList<?> list) {
for (Iterator<?> iter = list.iterator(); iter.hasNext();) {
Object o = iter.next();
if (o instanceof String) {
Log.d(TAG, "\t" + (String) o);
} else if (o instanceof Integer) {
Log.d(TAG, "\t" + (Integer) o);
} else if (o instanceof HashMap) {
Log.d(TAG, "\tHashMap");
#SuppressWarnings("unchecked")
HashMap<String, Object> hash = (HashMap<String, Object>) o;
for (Iterator<String> hashIter = hash.keySet().iterator(); hashIter.hasNext();) {
String key = hashIter.next();
Object value = hash.get(key);
if (value instanceof Integer) {
Log.d(TAG, "\t\t " + key + " = " + (Integer) value);
} else if (value instanceof String) {
Log.d(TAG, "\t\t " + key + " = " + (String) value);
}
}
}
}
}
/**
* Debug method. Iterate through all the keys in the HashMap (dict) and
* print the key names for each child HashMap (dict).
*/
#SuppressWarnings("unchecked")
private void printHashKeys(String key, HashMap<String, Object> map) {
Set<String> keys = map.keySet();
Log.d(TAG, key + " --> " + keys.toString());
for (Iterator<String> iter = keys.iterator(); iter.hasNext();) {
key = iter.next();
Object o = map.get(key);
if (o instanceof HashMap) {
printHashKeys(key, (HashMap<String, Object>) o);
}
}
}
}
Here is a sample plist file.
File : sample.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Version</key>
<integer>3</integer>
<key>Url</key>
<string>http://example.com/video.mp4</string>
<key>ExpireDate</key>
<date>2013-4-20T11:20:00Z</date>
<key>HighRes</key>
<false/>
<key>Browser</key>
<dict>
<key>VideoWidth</key>
<integer>640</integer>
<key>VideoHeight</key>
<integer>480</integer>
</dict>
</dict>
</plist>
Sample Usage :
ExamplePListParser mPListParser;
InputStream inputStream = new FileInputStream("/sdcard/sample.xml");
if(mPListParser == null) {
mPListParser = new ExamplePListParser(inputStream);
} else {
mPListParser.parse(inputStream);
}
int version = mPListParser.getVersion();
int height = mPListParser.getBrowserVideoHeight();
int width = mPListParser.getBrowserVideoWidth();
String url = mPListParser.getUrl();
String rating = mPListParser.getRating();
Date expireDate = mPListParser.getExpireDate();
boolean highRes = mPListParser.getHighRes();
// debug: print out keys and values
mPListParser.dumpKeys();
mPListParser.dumpValues();
Try it
http://code.google.com/p/xmlwise/
I'm testing it right now.
Since a plist is just an XML file, you can use any of the available XML parsers. Personally, I use XmlPullParser for small files.
Akos' code is imo the best response to this question so far. However, his algorithm does not work for nested arrays, and it doesn't support all PList tags. I'm working on a more generalized SAX-based implementation for PList parsing in Android. I'll post the code on my blog upon request.
The PList parser I promised can be found at: https://github.com/tenaciousRas/android-plist-parser
Have fun!
This isn't exactly what you asked but it is what I did rather than add a code dependency to my project.
You can convert the PList file to a JSON file and use the built in JSON parser. There is a tool to do this on OSX which comes with the developer tools (I think, that or it is just installed by default).
plutil -convert json Days.plist

Categories

Resources