Skip to content

Commit

Permalink
Merge ../UBL
Browse files Browse the repository at this point in the history
  • Loading branch information
Martin Kleppmann committed Mar 23, 2009
2 parents 8c776a2 + 99ca76a commit 94ca326
Show file tree
Hide file tree
Showing 5 changed files with 368 additions and 0 deletions.
24 changes: 24 additions & 0 deletions org/oaccounts/docgen/Attribute.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.oaccounts.docgen

import scala.collection.Map
import scala.xml.{Elem, Null, TopScope, PrettyPrinter, NodeSeq, EntityRef, Text, UnprefixedAttribute}

class Attribute(nsMapping: Map[String, Elem], defaultPrefix: String, definitionElem: Elem) {

val name = (definitionElem \ "@name").firstOption.getOrElse(error("Attribute has no name")).toString()

val typeName = (definitionElem \ "@type").firstOption.getOrElse(error("Attribute %s has no type".format(name))).toString()

val required = (definitionElem \ "@use").firstOption.flatMap(node => Some(node.toString())) match {
case Some(use) => use == "required"
case None => false
}

def htmlDescription: Elem =
<span class={ if (required) "att required" else "att optional" }>
{ name + "=" }<span class="content">{ typeName }</span>
</span>

override def toString() = "%s=\"<%s>\"".format(name, typeName)

}
33 changes: 33 additions & 0 deletions org/oaccounts/docgen/DocGen.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.oaccounts.docgen

import scala.xml.PrettyPrinter

/**
* Documentation generator for UBL and XBRL GL schemas.
*/
object DocGen {

def main(args: Array[String]) {

val invoice = new Element(None, None, new ScanImports("os-UBL-2.0/xsd/maindoc/UBL-Invoice-2.0.xsd"), "", "Invoice")
invoice.writeAllFiles()

/*
val sbInvoice = new Element(None, None, new ScanImports("os-UBL-2.0/xsd/maindoc/UBL-SelfBilledInvoice-2.0.xsd"), "", "SelfBilledInvoice")
sbInvoice.writeAllFiles()
val creditNote = new Element(None, None, new ScanImports("os-UBL-2.0/xsd/maindoc/UBL-CreditNote-2.0.xsd"), "", "CreditNote")
creditNote.writeAllFiles()
val sbCreditNote = new Element(None, None, new ScanImports("os-UBL-2.0/xsd/maindoc/UBL-SelfBilledCreditNote-2.0.xsd"), "", "SelfBilledCreditNote")
sbCreditNote.writeAllFiles()
val statement = new Element(None, None, new ScanImports("os-UBL-2.0/xsd/maindoc/UBL-Statement-2.0.xsd"), "", "Statement")
statement.writeAllFiles()
*/

//println(new PrettyPrinter(100, 4).format(el.example))

//println(el.childSequence(10).simpleContent.attributes.mkString(" "))
}
}
184 changes: 184 additions & 0 deletions org/oaccounts/docgen/Element.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package org.oaccounts.docgen

import scala.collection.mutable.ListBuffer
import scala.collection.Map
import scala.xml.{Elem, Null, TopScope, PrettyPrinter, NodeSeq, EntityRef, Text, UnprefixedAttribute}

class Element(parent: Option[Element], referrer: Option[Elem], nsMapping: Map[String, Elem], defaultPrefix: String, refName: String) {

val (nsPrefix, localName) = if (refName.contains(":")) {
val splitName = refName.split(":", 2)
(splitName(0), splitName(1))
} else (defaultPrefix, refName)

if (false) println(path) // for debugging

lazy val xsdDoc = nsMapping(nsPrefix)

lazy val definitionElem = (xsdDoc \ "element").find(elem => elem \ "@name" == localName).
getOrElse(error("Definition of element %s not found".format(localName)))

lazy val typeName = (definitionElem \ "@type").firstOption.
getOrElse(error("Definition of element %s has no type name".format(localName))).
toString()

lazy val simpleContent = new SimpleContent(nsMapping, nsPrefix, typeName)

lazy val typeElem = simpleContent.definitionElem

lazy val minOccurs = referrer match {
case Some(elem) => (elem \ "@minOccurs").firstOption match {
case Some(n) => n.toString.toInt
case None => 0
}
case None => 1
}

lazy val maxOccurs = referrer match {
case Some(elem) => (elem \ "@maxOccurs").firstOption match {
case Some(n) => if (n.toString == "unbounded") -1 else n.toString.toInt
case None => -1
}
case None => 1
}

lazy val childSequence: ListBuffer[Element] = {
var list = new ListBuffer[Element]
try {
val sequence = typeElem \ "sequence" \ "element"
for (child <- sequence) {
try {
val elRef = (child \ "@ref").firstOption.
getOrElse(error("Type %s has sequence/element child without @ref".format(typeName))).
toString()
val el = new Element(Some(this), Some(child.asInstanceOf[Elem]), nsMapping, nsPrefix, elRef)
if (isRecursive(el)) error("Recursive type definition in element %s".format(elRef))
list += el
} catch {
case e: NullPointerException => e.printStackTrace()
case e: RuntimeException => println("Warning: %s".format(e))
}
}
} catch {
case e: NullPointerException => e.printStackTrace()
case e: RuntimeException => println("Warning: %s".format(e))
}
list
}

def example: Elem = {
val children = childSequence.map(ch => ch.example)
Elem(null, localName, Null, TopScope, children:_*)
}

def printChildren() {
for (childElem <- typeElem \ "sequence" \ "element") {
println(childElem \ "@ref")
}
}

def path: String = parent match {
case Some(p) => "%s / %s".format(p.path, localName)
case None => localName
}

def isRecursive(newEl: Element): Boolean = {
(localName == newEl.localName) || (parent match {
case Some(p) => p.isRecursive(newEl)
case None => false
})
}

def isSimple: Boolean = childSequence.isEmpty

def htmlChildren: NodeSeq = childSequence.map(
el => {
var output = try {
if (el.isSimple) {
el.htmlElem
} else {
<div class={ if (el.minOccurs == 0) "elref optional" else "elref required" }>
<a href={ el.localName + ".html" } class="open">&lt;{ el.localName }...&gt;</a>
</div>
}
} catch {
case e: RuntimeException => {
println("Warning: %s".format(e))
<div class="elref">&lt;{ el.localName }...&gt;</div>
}
}

val multiClass = if (el.minOccurs == 0) "multi optional" else "multi required"
if (el.maxOccurs >= 2) {
output = <div class={ multiClass }>
{ output }
<div class="hint">{ el.localName } may occur up to { el.maxOccurs } times.</div>
</div>
}
if (el.maxOccurs < 0) {
output = <div class={ multiClass }>
{ output }
<div class="hint">{ el.localName } may occur any number of times.</div>
</div>
}
output
}
)

def htmlElem: Elem = {
val classes = if (minOccurs == 0) "el optional" else "el required"
if (isSimple && simpleContent.htmlAttributes.isEmpty) {
<div class={ classes }>
<span class="tag">&lt;{ localName }&gt;</span>
{ simpleContent.htmlContent }
<span class="tag">&lt;/{ localName }&gt;</span>
</div>
} else if (isSimple) {
<div class={ classes }>
<div class="tag">&lt;{ localName } { simpleContent.htmlAttributes }&gt;</div>
{ simpleContent.htmlContent }
<div class="tag">&lt;/{ localName }&gt;</div>
</div>
} else {
<div class={ classes }>
<div class="tag">&lt;{ localName }&gt;</div>
{ Elem(null, "div", new UnprefixedAttribute("class", "ch", Null), TopScope, htmlChildren:_*) }
<div class="tag">&lt;/{ localName }&gt;</div>
</div>
}
}

def writeIncludeFile() {
val writer = new java.io.FileWriter("doc/_includes/UBL_%s.html".format(localName))
//writer.write(new PrettyPrinter(100, 4).format(htmlElem))
writer.write(htmlElem.toString())
writer.close()
}

def writeFragmentFile() {
val writer = new java.io.FileWriter("doc/ubl/%s_frag.html".format(localName))
writer.write("---\nlayout: nil\n---\n")
writer.write("{%% include UBL_%s.html %%}".format(localName))
writer.close()
}

def writeFullFile() {
val writer = new java.io.FileWriter("doc/ubl/%s.html".format(localName))
writer.write("---\nlayout: ubldoc\ntitle: %s\n---\n".format(localName))
writer.write("{%% include UBL_%s.html %%}".format(localName))
writer.close()
}

def writeAllFiles() {
writeIncludeFile()
writeFragmentFile()
writeFullFile()
for (child <- childSequence) {
try {
child.writeAllFiles()
} catch {
case e: RuntimeException => println("Warning: %s".format(e))
}
}
}
}
68 changes: 68 additions & 0 deletions org/oaccounts/docgen/ScanImports.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package org.oaccounts.docgen

import scala.collection.Map
import scala.collection.mutable.{HashMap, HashSet}
import scala.xml.{Elem, XML}
import java.io.File
import java.net.URL

class ScanImports(filename: String) extends Map[String, Elem] {

var nsToURL = new HashMap[String, URL]
var nsToSchema = new HashMap[String, Elem]
var urlToSchema = new HashMap[URL, Elem]
var prefixToSchema = new HashMap[String, Elem]
var prefixUnknown = new HashSet[String]

prefixToSchema += "" -> loadFile(None, filename)


def loadFile(sourceURL: Option[URL], referenceURL: String): Elem = {
val targetURL = sourceURL match {
case Some(url) => new URL(url, referenceURL)
case None => new File(referenceURL).getCanonicalFile().toURI().toURL()
}
if (targetURL.getProtocol() != "file") error("Protocol %s is not supported for XML loading".format(targetURL.getProtocol()))

val xml = XML.loadFile(targetURL.getPath())
val targetNS = (xml \ "@targetNamespace").firstOption.
getOrElse(error("Schema %s has no targetNamespace attribute".format(targetURL))).
toString(true)
println("Namespace %s -> File %s".format(targetNS, targetURL.getPath()))

nsToURL += targetNS -> targetURL
nsToSchema += targetNS -> xml
urlToSchema += targetURL -> xml
xml
}

def get(prefix: String): Option[Elem] = {
if (prefixUnknown.contains(prefix)) return None

if (!prefixToSchema.contains(prefix)) {
// we do not yet have an import for this prefix. try to find one.
for (nsURI <- nsToSchema.values.map(schema => schema.getNamespace(prefix)).filter(uri => uri != null)) {
println("Namespace %s -> Prefix %s".format(nsURI, prefix))
// the schema for that namespace has already been loaded
if (nsToSchema.contains(nsURI)) {
prefixToSchema += prefix -> nsToSchema.get(nsURI).get
}

// not loaded: try to find an import for that namespace URI
for ((schemaURL, schema) <- urlToSchema) {
for (location <- (schema \\ "import").filter(el => el \ "@namespace" == nsURI) \ "@schemaLocation") {
prefixToSchema += prefix -> loadFile(Some(schemaURL), location.toString())
}
}
}
}

if (!prefixToSchema.contains(prefix)) prefixUnknown += prefix
prefixToSchema.get(prefix)
}

def size: Int = prefixToSchema.size

def elements: Iterator[(String, Elem)] = prefixToSchema.elements

}
59 changes: 59 additions & 0 deletions org/oaccounts/docgen/SimpleContent.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package org.oaccounts.docgen

import scala.collection.Map
import scala.xml.{Elem, Null, TopScope, PrettyPrinter, NodeSeq, EntityRef, Text, UnprefixedAttribute}

class SimpleContent(nsMapping: Map[String, Elem], defaultPrefix: String, refName: String) {

val (nsPrefix, localName) = if (refName.contains(":")) {
val splitName = refName.split(":", 2)
(splitName(0), splitName(1))
} else (defaultPrefix, refName)

val xsdDoc = nsMapping(nsPrefix)

lazy val definitionElem = (xsdDoc \ "complexType").find(elem => elem \ "@name" == localName).
getOrElse((xsdDoc \ "simpleType").find(elem => elem \ "@name" == localName).
getOrElse(error("Type %s:%s not found".format(nsPrefix, localName)))).
asInstanceOf[scala.xml.Elem]

lazy val baseName = (definitionElem \\ "extension" \ "@base").firstOption match {
case Some(name) => Some(name.toString())
case None => (definitionElem \\ "restriction" \ "@base").firstOption match {
case Some(name) => Some(name.toString())
case None => None
}
}

lazy val baseObj = try {
baseName.flatMap(name => Some(new SimpleContent(nsMapping, nsPrefix, name)))
} catch {
case e: java.util.NoSuchElementException => None // if namespace prefix cannot be resolved to a schema
}

lazy val finalBaseName: String = baseObj match {
case Some(base) => try {
base.finalBaseName
} catch {
case e: java.util.NoSuchElementException => baseName.get
}
case None => baseName.get
}

lazy val ownAttrs =
(definitionElem \\ "attribute").map(el => new Attribute(nsMapping, nsPrefix, el.asInstanceOf[Elem]))

private def mergeAttrs(a1: Seq[Attribute], a2: Seq[Attribute]): Seq[Attribute] = {
a1 ++ (a2.filter(a2a => a1.filter(a1a => a1a.name == a2a.name).isEmpty))
}

lazy val attributes: Seq[Attribute] = baseObj match {
case Some(base) => mergeAttrs(base.attributes, ownAttrs)
case None => ownAttrs
}

lazy val htmlAttributes: NodeSeq = attributes.map(a => a.htmlDescription)

lazy val htmlContent = <span class="content">{ finalBaseName }</span>

}

0 comments on commit 94ca326

Please sign in to comment.