-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
368 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(" ")) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"><{ el.localName }...></a> | ||
</div> | ||
} | ||
} catch { | ||
case e: RuntimeException => { | ||
println("Warning: %s".format(e)) | ||
<div class="elref"><{ el.localName }...></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"><{ localName }></span> | ||
{ simpleContent.htmlContent } | ||
<span class="tag"></{ localName }></span> | ||
</div> | ||
} else if (isSimple) { | ||
<div class={ classes }> | ||
<div class="tag"><{ localName } { simpleContent.htmlAttributes }></div> | ||
{ simpleContent.htmlContent } | ||
<div class="tag"></{ localName }></div> | ||
</div> | ||
} else { | ||
<div class={ classes }> | ||
<div class="tag"><{ localName }></div> | ||
{ Elem(null, "div", new UnprefixedAttribute("class", "ch", Null), TopScope, htmlChildren:_*) } | ||
<div class="tag"></{ localName }></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)) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
|
||
} |