I'm trying to store certain lines from an .ics file to separate strings depending on their contents. I've been able to convert an .ics file to a string, but I am having difficulty searching it line by line to find certain keywords.
The string (and file) contains:
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//School of Rochester NY |-ECPv4.8.1//NONSGML v1.0//EN
CALSCALE:GREGORIAN
METHOD:PUBLISH
X-WR-CALNAME:School of Rochester NY |
I've been able to display the text in the logcat, but I have not been able to save the lines as separate strings.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var textView = findViewById<TextView>(R.id.textView)
val file_name = "education-1e1a4bdab8e.ics"
val ics_string = application.assets.open(file_name).bufferedReader().use {
it.readText()
}
Log.i("TAG", ics_string)
textView.text = ics_string
if (ics_string.contains("BEGIN:VCALENDAR", ignoreCase = true))
{
Log.i("TAG", "contains event")
}
}
The logcat confirms that the text is in the document, but not which line.
Is there any way to add lines of a text as separate strings?
Just looking at the BufferedReader you have already 4 functions that all give you the lines.
readLines gives you a List<String> containing all the lines
useLines lets you use a sequence of lines which you can then transform and assures that after calling it, the reader is closed
lineSequence() returns a sequence of the lines, but does not close the reader after calling it
lines() returns a Stream<String> containing the lines and basically comes from the BufferedReader itself. As you are using Kotlin you probably do not want to use this method.
useLines and readLines are also available on File itself
As I am not sure what you really want to accomplish I recommend you start with readLines directly. The ics-file is usually rather small and with the lines you can still filter/map whatever you want. The next best candidate then is probably either useLines or lineSequence. It really depends on what you do next.
You can use the extension function on String, lines(), something like this:
fun lines() {
val text = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//School of Rochester NY |-ECPv4.8.1//NONSGML v1.0//EN
CALSCALE:GREGORIAN
METHOD:PUBLISH
X-WR-CALNAME:School of Rochester NY |"""
val foundLines = text.lines().map {
it.contains("BEGIN:VCALENDAR") to it
}.filter { (contains, _) -> contains }
.map { it.second }
println(foundLines)
}
fun main() {
lines()
}
Related
In our class this week, our assignment is to create a simple cake-baking app. There are two edit text fields(wetText, dryText) in which the user can input ingredients to add to the cake. There is a mixbutton that is clicked after adding the ingredients. On the mixbutton click, my goal is to list the added ingredients from the editText into a new textView(cakeText) as such:
You added --- to the batter!
You added --- to the batter!
You added --- to the batter!
etc.
We're supposed to use a for-loop, and I think I may be on the right track by using an array. The batterList was my most recent attempt at this, so I know it's wrong, but I'd love to know how to fix it! I've been working at it for hours and have gotten close, but not close enough. I hope this makes sense. My mind isn't working right at this point. Any advice would be greatly appreciated!
val wetList = mutableListOf<String>()
val dryList = mutableListOf<String>()
val batterList = arrayOf(wetList)
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
fun wetButtonTapped(view: View) {
wetList.add(wetText.text.toString())
wetText.text.clear()
ingredientList.text = "You have ${wetList.count()} wet ingredients \n You have ${dryList.count()} dry indredients"
}
fun dryButtonTapped(view: View) {
dryList.add(dryText.text.toString())
dryText.text.clear()
ingredientList.text = "You have ${wetList.count()} wet ingredients \n You have ${dryList.count()} dry indredients"
}
fun mixButtonTapped(view: View) {
//cakeText.text = "You added ${wetList}"
for (item in batterList){
cakeText.text = "You added $item to the batter!"
}
as far as i understand you want to show all added ingredients in single text view. so instead of declaring a new array which can be sometime unmanageable i will directly use both of array and StringBuilder class for building the whole string
fun mixButtonTapped(view: View) {
val stringBuilder = StringBuilder()
// here (wetList + dryList) will be merged into single list
for (item in (wetList + dryList))
stringBuilder.append("You added $item to the batter!\n")
cakeText.text = stringBuilder.toString()
}
so instead of managing third array i will directly use both array and dispose the merged array when my task is done in this case it will be when the loop is completed.
You're always assigning the last line of your intended list into cakeText.
Try this:
cakeText.text = "${cakeText.text}\nYou added $item to the batter!"
This should add the items one by one in the text.
Also, you may need to change batterList from val to var and reassign it on mixButtonTapped. So final code should look like this:
var batterList = arrayOf(wetList)
...
fun mixButtonTapped(view: View) {
batterList = arrayOf(wetList)
for (item in batterList){
cakeText.text = "${cakeText.text}\nYou added $item to the batter!"
}
}
I have an output textView which I am successfully printing one calculation per new line. I expect and intend on the same occurring when I convert to PDF or print. However when I enter the string value into the PDF creation method and click the button to generate PDF from textView the pdf is all squished into a paragraph instead of one single column with new lines per calculation.
So far I've tried a few different ways of string interpolation, and many different combinations of altering the setContent method as well as altering the exportText value with no success or change...
I am using this dependancy for the PDF writing: https://github.com/RakeshPonna/PdfFileGenerator
My code for the button press:
// Print version button
printableVersion.setOnClickListener {
val exportText = testOutput.text.toString()
val exportFilename = ""
CreatePdf(this)
.setPdfName("Coursing output")
.openPrintDialog(true)
.setContentBaseUrl(null)
.setPageSize(PrintAttributes.MediaSize.ISO_A4)
.setContent(exportText)
.setFilePath(pdfFilePath = exportFilename)
.setCallbackListener(object : CreatePdf.PdfCallbackListener {
override fun onFailure(errorMsg: String) {
makeText(this#MetricEvenGauge, errorMsg, LENGTH_SHORT).show()
}
override fun onSuccess(filePath: String) {
makeText(this#MetricEvenGauge, "Pdf Saved at: $filePath", LENGTH_SHORT).show()
}
})
.create()
}
My textView in app:
The PDF/print screen:
Appreciate anyone's input and help :)
EDIT: After some input the problem has evolved into how to make this line break statement from the method:
for (index in 1..testNumber.toInt()) {
calculation += "\nTest #$index = ${index * (testLevel +
attempt)}"
Work with html, this don't work:
// Print version button- working of PDF printing
printableVersion.setOnClickListener {
val exportText = testOutput.text.toString()
val htmlText = "<html><br>$exportText<br></html>"
val exportFilename = ""
CreatePdf(this)
.setPdfName("Coursing output")
.openPrintDialog(true)
.setContentBaseUrl(null)
.setPageSize(PrintAttributes.MediaSize.ISO_A4)
.setContent(htmlText)
.setFilePath(pdfFilePath = exportFilename)
.setCallbackListener(object :
CreatePdf.PdfCallbackListener {
override fun onFailure(errorMsg: String) {
makeText(this#MetricEvenGauge, errorMsg,
LENGTH_SHORT).show()
}
override fun onSuccess(filePath: String) {
makeText(
this#MetricEvenGauge,
"Pdf Saved at: $filePath",
LENGTH_SHORT
).show()
}
})
.create()
}
The library you're using simply loads the input into a WebView, so it would take HTML formatting. Use <br> for line breaks.
I have a mutable list of strings and I'm trying to remove a word from each of them. The problem is that I don't believe the word is being removed from each line.
for (item in stringLines) {
when {
item.contains("SUMMARY") -> eventLines.add(item)
item.contains("DTSTART") -> startDateLines.add(item)
item.contains("DTEND") -> endDateLines.add(item)
//item.contains("URL:") -> locationLines.add(item)
//item.contains("CATEGORIES") -> categoriesLines.add(item)
}
}
for (item in eventLines) {
when{
item.contains("SUMMARY") -> item.removePrefix("SUMMARY")
}
}
The string is
SUMMARY WINTER FORMAL
and I need
WINTER FORMAL
but whatever reason it just doesn't appear. I know how to do this in java but I have no idea how to do this in Kotlin and I could not find any function that was able to accomplish this. What am I missing?
removePrefix removes the specific part of the string only if that is the prefix. Obviously, this is not your case. You could do a split, filter, join sequence to yield your expected result.
println("SUMMARY WINTER FORMAL".split(" ").filter { it != "SUMMARY" }.joinToString(" "))
//output : "WINTER FORMAL"
But if you have multiple contiguous spaces, they will become one space in the output. Also this method does not work if "SUMMARY" is not surrounded by spaces, e.g. "SUMMARYWINTERFORMAL" won't be changed.
You can remove part of the string by replacing it with empty string, but note that this will keep any surrounding characters including the surrounding spaces.
println("SUMMARY WINTER FORMAL".replace("SUMMARY",""))
//output : " WINTER FORMAL"
Note that there is a space in front of "WINTER"
If you wan't to include the spaces if any, using Regular Expression would do:
println("SUMMARY WINTER FORMAL".replace("\\s*SUMMARY\\s*".toRegex(),""))
//output : "WINTER FORMAL"
println("SUMMARYWINTERFORMAL".replace("\\s*SUMMARY\\s*".toRegex(),""))
//output : "WINTERFORMAL"
println(" SUMMARY WINTER FORMAL".replace("\\s*SUMMARY\\s*".toRegex(),""))
//output : "WINTER FORMAL"
You can modify the regular expression even more to better suit your use case.
Strings in Kotlin (and Java) are immutable. So item.removePrefix returns a new String and you have to update the list or create a new list like this:
for (item in eventLines) {
when {
item.contains("SUMMARY") ->
eventLinesWithPrefixRemoved.add(item.removePrefix("SUMMARY"))
else -> eventLinesWithPrefixRemoved.add(item)
}
}
If you want only to remove 'SUMMARY' from the string, you can use this case
val l = listOf("SUMMARY WINTER FORMAL", "SUMMARY FORMAL", "WINTER FORMAL", "SUMMARY WINTER")
println(
l.map {
it.split(" ")
.filter { s -> s != "SUMMARY" }
.joinToString(" ")
}
) // puts [WINTER FORMAL, FORMAL, WINTER FORMAL, WINTER]
So I have a textField where you should enter your "coded" text and get it translated back to non-coded language by using .replace to remove certain characters. But I can't get it to work.
There is a kids "code-language" where you take a word, like cat and for every consonant you add an "o" and the consonant again. So a "b" would be "bob". With vowels they just stay as they are. Cat would be cocatot.
fun translateBack(view : View) {
val konsonanter = "bcdfghjklmnpqrstvwxzBCDFGHJKLMNPQRSTVWXZ"
var input = editText.text.toString()
var emptyString = ""
for(i in konsonanter) {
val find_text = i + "o" + i
var conso = i.toString()
textView.text = input.replace(find_text, conso, false)
}
}
Would like for it to remove the two following letters for every consonant (if possible). So if i enter "cocowow" I should get out "cow". Right now I just get back what I enter in the textField...
Use a forEach loop through the chars of input and replace:
konsonanter.forEach { input = input.replace("${it}o${it}", "${it}", false) }
textView.text = input
The issue is that you're setting the text in textView in every loop, and never updating input. So you're essentially only seeing the result of the replace call that happens with the "ZoZ" and "Z" parameters at the end of the loop, with input still being the original string.
Instead, you can keep updating input and then set the text when you're done:
val konsonanter = "bcdfghjklmnpqrstvwxzBCDFGHJKLMNPQRSTVWXZ"
var input = editText.text.toString()
var emptyString = ""
for (i in konsonanter) {
val find_text = i + "o" + i
val conso = i.toString()
input = input.replace(find_text, conso, false)
}
textView.text = input
If you use the replace function with a Regex and a transform function as parameters you can create a really concise completely self-containing extension function:
fun String.translateBack() = with(Regex("([bcdfghjklmnpqrstvwxz])o\\1", RegexOption.IGNORE_CASE)) {
replace(this) { "${it.value.first()}" }
}
Explanation:
The Regex will match all same consonants (no matter the case) around an "o".
To ensure that the consonant before and after the "o" are the same a backreference to the first group was used.
So, this will also work for cases like "coCatot".
Usage:
println("bob".translateBack()) // b
println("cocatot".translateBack()) // cat
println("coCatot".translateBack()) // cat
I'm writing a simple import application and need to read a CSV file, show result in a grid and show corrupted lines of the CSV file in another grid.
Is there any built-in lib for it or any easy pythonic-like way?
I'm doing it on android.
[Edit October 2019] A couple of months after I wrote this answer, Koyama Kenta wrote a Kotlin targeted library which can be found at https://github.com/doyaaaaaken/kotlin-csv and which looks much better to me than opencsv.
Example usage: (for more info see the github page mentioned)
import com.github.doyaaaaaken.kotlincsv.dsl.csvReader
fun main() {
csvReader().open("src/main/resources/test.csv") {
readAllAsSequence().forEach { row ->
//Do something
println(row) //[a, b, c]
}
}
}
For a complete minimal project with this example, see https://github.com/PHPirates/kotlin-csv-reader-example
Old answer using opencsv:
As suggested, it is convenient to use opencsv. Here is a somewhat minimal example:
// You can of course remove the .withCSVParser part if you use the default separator instead of ;
val csvReader = CSVReaderBuilder(FileReader("filename.csv"))
.withCSVParser(CSVParserBuilder().withSeparator(';').build())
.build()
// Maybe do something with the header if there is one
val header = csvReader.readNext()
// Read the rest
var line: Array<String>? = csvReader.readNext()
while (line != null) {
// Do something with the data
println(line[0])
line = csvReader.readNext()
}
As seen in the docs when you do not need to process every line separately you can get the result in the form of a Map:
import com.opencsv.CSVReaderHeaderAware
import java.io.FileReader
fun main() {
val reader = CSVReaderHeaderAware(FileReader("test.csv"))
val resultList = mutableListOf<Map<String, String>>()
var line = reader.readMap()
while (line != null) {
resultList.add(line)
line = reader.readMap()
}
println(resultList)
// Line 2, by column name
println(resultList[1]["my column name"])
}
Dependency for Gradle: compile 'com.opencsv:opencsv:4.6' or for Gradle Kotlin DSL: compile("com.opencsv:opencsv:4.6") (as always, check for latest version in docs).
In terms of easiness, kotlin written csv library is better.
For example, you can write code in DSL like way with below library that I created:
https://github.com/doyaaaaaken/kotlin-csv
csvReader().open("test.csv") {
readAllAsSequence().forEach { row ->
//Do something with the data
println(row)
}
}
Use opencsv.
This is gonna work like a charm for reading a CSV file.
As far as logging the corrupted lines is concerned you can do it using this logic.
while(input.hasNextLine())
{
try
{
//execute commands by reading them using input.nextLine()
}
catch (ex: UserDefinedException)
{
//catch/log the exceptions you're throwing
// log the corrupted line the continue to next iteration
}
}
Hope this helps.
I used net.sourceforge.javacsv with my Kotlin code for parsing CSV files. It is a "java" library but within kotlin it is fairly straightforward to work with it like
val reader = CsvReader("/path/to/file.csv").apply {
trimWhitespace = true
skipEmptyRecords = true
readHeaders()
}
while (reader.readRecord()) {
// do whatever
}
Frankly speaking, it is quite easy to make a simple reader in Kotlin using modern Java features, check this (REMEMBER to handle BOM :-)):
fun processLineByLine(csv: File, processor: (Map<String, String>) -> Unit) {
val BOM = "\uFEFF"
val header = csv.useLines { it.firstOrNull()?.replace(BOM, "")?.split(",") }
?: throw Exception("This file does not contain a valid header")
csv.useLines { linesSequence ->
linesSequence
.drop(1)
.map { it.split(",") }
.map { header.zip(it).toMap() }
.forEach(processor)
}
}
Than you can use it as follows (depends on your file structure):
processLineByLine(File("./my-file.csv")) { row ->
println("UserId: ${row["userId"]}")
println("Email: ${row["email"]}")
}
If you prefer to use your own data class for each row you should have a look at my solution https://github.com/gmuth/ipp-client-kotlin/blob/master/src/main/kotlin/de/gmuth/csv/CSVTable.kt
data class User(
val name: String,
val phone: String,
val email: String
) {
constructor(columns: List<String>) : this(
name = columns[0],
phone = columns[1],
email = columns[2]
)
}
CSVTable.print(FileInputStream("users.csv"))
val userList = CSVTable(FileInputStream("users.csv"), ::User).rows
I know i'm a bit late, but I recently had problems with parsing CSV and there seemed to be no library good enough for what I was looking for, so I created my own called Kotlin CSV stream.
This library is special because it doesn't throw exceptions on an invalid input, but returns in the result instead, which might be useful in some cases.
Here is an example of how easy it is to use
val reader = CsvReader()
.readerForType<CsvPerson>()
val people = reader.read(csv).map { it.getResultOrThrow() }.toList()
For version of commons-csv version 1.9.0, have implemented below code to get results.
It uses CSVBuilder and CSVFormat to get records with skip headers and auto-identify headers on basis of first row.
fun csvReader(file: MultipartFile): ResultListObject? {
var result = ResultListObject()
var csvFormat=CSVFormat.Builder.create().setHeader().setSkipHeaderRecord(true).build()
var csvRecords = CSVParser(file.inputStream.bufferedReader(), csvFormat)
csvRecords.forEach{csvRecords->
rowRecord.field1=records.get("field1")
rowRecord.field2=records.get("field2")
...
...
result.add(rowRecord)
}
return result
}