Andrey Hihlovskiy

Professional blog on groovy, gradle, Java, Javascript and other stuff.

Tag Archives: xml

Groovy DSL == thermonuclear way of writing XML

import groovy.xml.MarkupBuilder

String createEAD(Closure closure) {
  def writer = new StringWriter()
  def xml = new MarkupBuilder(writer)
  xml.mkp.xmlDeclaration(version: '1.0', encoding: 'UTF-8')
  xml.'ead:ead'('xmlns:ead': 'urn:isbn:1-931666-22-9') {
    closure.delegate = new Object() {
      def text(Map attrs, content) {
        def a = attrs.keySet().find { it in ['bold', 'italic', 'underline'] }
        if(a && attrs[a]) {
          xml.'ead:emph' render: a, {
            text attrs.findAll({ it.key != a }), content
          }
        } else
          text content
      }
      def text(content) {
        if(content instanceof String)
          xml.mkp.yield content
        else if(content instanceof Closure)
          content()
      }
    }
    closure()
  }
  return writer.toString()
}

println createEAD {
  text bold: true, italic: true, {
    text 'Hello, '
    text underline: true, 'world!'
  }
}

expected output:

<?xml version='1.0' encoding='UTF-8'?>
<ead:ead xmlns:ead='urn:isbn:1-931666-22-9'>
  <ead:emph render='bold'>
    <ead:emph render='italic'>Hello, 
      <ead:emph render='underline'>world!</ead:emph>
    </ead:emph>
  </ead:emph>
</ead:ead>

A happier, groovier way to parse RTF: apache_tika + XmlSlurper

I discovered a new, easier way to parse RTF in java/groovy programs. Consider the following sequence:

1. Instantiate XmlSlurper

2. Instantiate RTFParser (of Apache Tika)

3. Parse RTF (either file or string), passing XmlSlurper to RTFParser (such passing is possible, because RTFParser expects ContentHandler interface, which is implemented by XmlSlurper).

4. Traverse RTF content groovy-style: each, find, findAll, etc.

The example:


package org.akhikhl.test
import org.apache.tika.metadata.Metadata
import org.apache.tika.parser.rtf.RTFParser
class ParseRtf {
def parse(String rtfText) {
// not validating, not ns-aware
XmlSlurper slurper = new XmlSlurper(false, false)
InputStream rtfStream = new ByteArrayInputStream(value.getBytes())
new RTFParser().parse(rtfStream, slurper, new Metadata())
slurper.document.'body'.p.each { p ->
println "Got paragraph: ${p.text()}"
}
}
}

view raw

ParseRtf.groovy

hosted with ❤ by GitHub

Programmatic configuration of slf4j/logback

Now I have experience with programmatic configuration of slf4j/logback.
Task
A program must open separate log file for each processed input file.
Solution for task
Instead of configuring logback via xml, the one needs to “manually” instantiate encoders, appenders and loggers, then configure and link them together.
Caveat 1
Logback goes crazy on attempt to share encoder (i.e. PatternLayoutEncoder) between appenders.
Solution for caveat 1
Create separate encoder for each appender.
Caveat 2
Logback refuses to log anything, if encoders and appenders are not associated with logging context.
Solution for caveat 2
Call setContext on each encoder and appender, passing LoggerFactory as a parameter.
Caveat 3
Logback refuses to log anything, if encoders and appenders are not started.
Solution for caveat 3
encoders and appenders need to be started in the correct order, i.e. first encoders, then appenders.
Caveat 4
RollingPolicy objects (i.e. TimeBasedRollingPolicy) produce strange error messages like “date format not recognized”, when they are not attached to the same context as appender.
Solutin for caveat 4
call setContext on RollingPolicy same way as on encoders and appenders.

Here is working example of “manual” logback configuration:


package testpackage
import ch.qos.logback.classic.Level
import ch.qos.logback.classic.Logger
import ch.qos.logback.classic.LoggerContext
import ch.qos.logback.classic.encoder.PatternLayoutEncoder
import ch.qos.logback.core.ConsoleAppender
import ch.qos.logback.core.rolling.RollingFileAppender
import ch.qos.logback.core.rolling.TimeBasedRollingPolicy
import org.slf4j.LoggerFactory
class TestLogConfig {
public static void main(String[] args) {
LoggerContext logCtx = LoggerFactory.getILoggerFactory()
PatternLayoutEncoder logEncoder = new PatternLayoutEncoder()
logEncoder.setContext(logCtx)
logEncoder.setPattern('%-12date{YYYY-MM-dd HH:mm:ss.SSS} %-5level – %msg%n')
logEncoder.start()
ConsoleAppender logConsoleAppender = new ConsoleAppender()
logConsoleAppender.setContext(logCtx)
logConsoleAppender.setName('console')
logConsoleAppender.setEncoder(logEncoder)
logConsoleAppender.start()
logEncoder = new PatternLayoutEncoder()
logEncoder.setContext(logCtx)
logEncoder.setPattern('%-12date{YYYY-MM-dd HH:mm:ss.SSS} %-5level – %msg%n')
logEncoder.start()
RollingFileAppender logFileAppender = new RollingFileAppender()
logFileAppender.setContext(logCtx)
logFileAppender.setName('logFile')
logFileAppender.setEncoder(logEncoder)
logFileAppender.setAppend(true)
logFileAppender.setFile('logs/logfile.log')
TimeBasedRollingPolicy logFilePolicy = new TimeBasedRollingPolicy()
logFilePolicy.setContext(logCtx)
logFilePolicy.setParent(logFileAppender)
logFilePolicy.setFileNamePattern('logs/logfile-%d{yyyy-MM-dd_HH}.log')
logFilePolicy.setMaxHistory(7)
logFilePolicy.start()
logFileAppender.setRollingPolicy(logFilePolicy)
logFileAppender.start()
Logger log = logCtx.getLogger("Main")
log.additive = false
log.level = Level.INFO
log.addAppender(logConsoleAppender)
log.addAppender(logFileAppender)
}
}

My conclusion: logback is much easier to configure via XML. “Manual” configuration is rather tedious task and should be avoided, unless it is dictated by project needs.

Disappointment with groovy/XmlSlurper

Greatest disappointment with groovy/XmlSlurper: it does not read/interpret XML comments. Quite critical for massive XML processing/transformations, when it is necessary to keep change delta to minimum.
In the last project I had to recede to JDOM2 – it reads, interprets and writes XML comments without problems. Sad, volume of code doubles compared to XmlSlurper.

groovy XmlParser and XmlSlurper

I am absolutely astonished by functionality of groovy classes XmlParser and XmlSlurper. The both are similar to each other, with one important difference: XmlParser is more DOM-like (all data in memory), while XmlSlurper is more SAX-like (data parsing delayed until needed).
The most charming thing is how both work together with closures, regexps and collection methods. I am seriously thinking about shifting all XML-specific code to these facilities.

http://groovy.codehaus.org/Reading+XML+using+Groovy%27s+XmlParser

http://groovy.codehaus.org/Reading+XML+using+Groovy%27s+XmlSlurper