Skip to content

Commit

Permalink
Fixing compileRNG API.
Browse files Browse the repository at this point in the history
  • Loading branch information
obuchtala committed Apr 28, 2019
1 parent 015efb8 commit 67431c3
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 64 deletions.
12 changes: 3 additions & 9 deletions bundler/_compileSchema.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
const path = require('path')
const fs = require('fs')
const { compileRNG } = require('..')
const path = require('path')
const { _compileRNG } = require('..')

module.exports = function _compileSchema (rngFile, searchDirs, options = {}) {
const rngDir = path.dirname(rngFile)
// Note: instead of using the absolute path to the rngFile
// we register the dir as first search directory and use the
// the basename (without dir) of the rng file relying on the rng lookup mechanism
searchDirs.unshift(rngDir)
const rngFileBase = path.basename(rngFile)
return compileRNG(fs, searchDirs, rngFileBase)
return _compileRNG(fs, path, rngFile, searchDirs)
}
18 changes: 3 additions & 15 deletions src/compileRNG.js → src/_compileRNG.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isString, DefaultDOMElement, nameWithoutNS } from 'substance'
import { DefaultDOMElement, nameWithoutNS } from 'substance'
import XMLSchema from './XMLSchema'
import DFA from './DFA'
import { createExpression, Token, Choice, Sequence, Optional, Plus, Kleene, Interleave } from './RegularLanguage'
Expand All @@ -12,28 +12,16 @@ const TEXT = DFA.TEXT
We use regular RNG, with slight restrictions plus custom extensions,
and compile it into our internal format.
*/
export default function compileRNG (fs, searchDirs, entry) {
let rng
// used for testing
if (arguments.length === 1 && isString(arguments[0])) {
rng = DefaultDOMElement.parseXML(arguments[0])
} else {
rng = _loadRNG(fs, searchDirs, entry)
}

export default function _compileRNG (fs, path, rngFile, searchDirs) {
let rng = _loadRNG(fs, path, rngFile, searchDirs)
let grammar = rng.find('grammar')
if (!grammar) throw new Error('<grammar> not found.')

// collect all definitions, allowing for custom overrides
_registerDefinitions(grammar)

// turn the RNG schema into our internal data structure
let transformedGrammar = _transformRNG(grammar)

// console.log(prettyPrintXML(transformedGrammar))

let xmlSchema = _compile(transformedGrammar)

return xmlSchema
}

Expand Down
19 changes: 11 additions & 8 deletions src/_expandIncludes.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import { DefaultDOMElement } from 'substance'
import _lookupRNG from './_lookupRNG'

export default function _expandIncludes (fs, searchDirs, root) {
let includes = root.findAll('include')
export default function _expandIncludes (fs, path, currentDir, searchDirs, grammarEl) {
let includes = grammarEl.findAll('include')
if (includes.length === 0) return false

includes.forEach((include) => {
includes.forEach(include => {
const parent = include.parentNode
const href = include.attr('href')
const rngPath = _lookupRNG(fs, searchDirs, href)
const rngPath = _lookupRNG(fs, href, currentDir, searchDirs)
if (!rngPath) throw new Error(`Could not find ${href}`)
const rngStr = fs.readFileSync(rngPath, 'utf8')
const rng = DefaultDOMElement.parseXML(rngStr, 'full-doc')
const grammar = rng.find('grammar')
if (!grammar) throw new Error('No grammar element found')
grammar.children.forEach((child) => {
const _grammarEl = rng.find('grammar')
if (!_grammarEl) throw new Error('No grammar element found')
let rngDir = path.dirname(rngPath)
// expand the grammar recursively
_expandIncludes(fs, path, rngDir, searchDirs, _grammarEl)
// now replace the include element with the content of the expanded grammar
_grammarEl.children.forEach((child) => {
parent.insertBefore(child, include)
})
include.remove()
Expand Down
12 changes: 5 additions & 7 deletions src/_loadRNG.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import { DefaultDOMElement, isArray } from 'substance'
import _expandIncludes from './_expandIncludes'
import _lookupRNG from './_lookupRNG'

/*
Loads a RNG with all dependencies into a DOM element
*/
export default function _loadRNG (fs, searchDirs, rngFile) {
export default function _loadRNG (fs, path, rngFile, searchDirs) {
if (!isArray(searchDirs)) searchDirs = [searchDirs]
let rngPath = _lookupRNG(fs, searchDirs, rngFile)
if (!rngPath) throw new Error('Could not resolve RNG: ' + rngFile)
let rngStr = fs.readFileSync(rngPath, 'utf8')
let rngDir = path.dirname(rngFile)
let rngStr = fs.readFileSync(rngFile, 'utf8')
const rng = DefaultDOMElement.parseXML(rngStr, 'full-doc')
// first pull in all includes (recursively)
while (_expandIncludes(fs, searchDirs, rng)) { /* nothing */ }
const grammarEl = rng.find('grammar')
_expandIncludes(fs, path, rngDir, searchDirs, grammarEl)
return rng
}
28 changes: 24 additions & 4 deletions src/_lookupRNG.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,28 @@
export default function _lookupRNG (fs, searchDirs, file) {
/**
* Look up a RNG file from the current directory or a list of search directories.
*
* @param {*} fs
* @param {*} rngFileName
* @param {*} currentDir
* @param {*} searchDirs
*/
export default function _lookupRNG (fs, rngFileName, currentDir, searchDirs) {
let rngPath
// 1. Try if the file can be found directly
rngPath = rngFileName
if (fs.existsSync(rngPath)) {
return rngPath
}
// 2. Try the current directory
rngPath = currentDir + '/' + rngFileName
if (fs.existsSync(rngPath)) {
return rngPath
}
// 3. Try the search directories
for (let i = 0; i < searchDirs.length; i++) {
let absPath = searchDirs[i] + '/' + file
if (fs.existsSync(absPath)) {
return absPath
rngPath = searchDirs[i] + '/' + rngFileName
if (fs.existsSync(rngPath)) {
return rngPath
}
}
}
2 changes: 1 addition & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
export { default as _analyzeSchema } from './_analyzeSchema'
export { default as _compileRNG } from './_compileRNG'
export { default as _expandIncludes } from './_expandIncludes'
export { default as _isTextNodeEmpty } from './_isTextNodeEmpty'
export { default as _loadRNG } from './_loadRNG'
export { default as _lookupRNG } from './_lookupRNG'
export { default as compileRNG } from './compileRNG'
export { default as DFA } from './DFA'
export { default as DFABuilder } from './DFABuilder'
export { default as validateXML } from './validateXML'
Expand Down
92 changes: 72 additions & 20 deletions test/XMLSchema.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { test as _test } from 'substance-test'
import { DefaultDOMElement } from 'substance'
import { compileRNG, validateXML, serializeXMLSchema, deserializeXMLSchema } from '../index.es.js'
import { _compileRNG, validateXML, serializeXMLSchema, deserializeXMLSchema } from '../index.es.js'

function XMLSchemaTests (withSerialization) {
const test = function (title, fn) {
Expand Down Expand Up @@ -37,7 +37,7 @@ function XMLSchemaTests (withSerialization) {
</start>
</grammar>
`
let xmlSchema = _compileRNG(RNG, withSerialization)
let xmlSchema = _compileRNGString(RNG, withSerialization)
let doc, result

doc = DefaultDOMElement.parseXML(`
Expand Down Expand Up @@ -91,7 +91,7 @@ function XMLSchemaTests (withSerialization) {
</start>
</grammar>
`
let xmlSchema = _compileRNG(RNG, withSerialization)
let xmlSchema = _compileRNGString(RNG, withSerialization)
let doc, result
doc = DefaultDOMElement.parseXML(`
<foo>
Expand Down Expand Up @@ -138,23 +138,23 @@ function XMLSchemaTests (withSerialization) {
</grammar>
`
test('XMLSchema: Text elements should be allowed to be empty', t => {
let xmlSchema = _compileRNG(FOO)
let xmlSchema = _compileRNGString(FOO)
let doc = DefaultDOMElement.parseXML(`<foo></foo>`)
let result = validateXML(xmlSchema, doc)
t.ok(result.ok, 'empty text element is valid')
t.end()
})

test('XMLSchema: Text elements should allow for CDATA', t => {
let xmlSchema = _compileRNG(FOO, withSerialization)
let xmlSchema = _compileRNGString(FOO, withSerialization)
let doc = DefaultDOMElement.parseXML(`<foo><![CDATA[x^2 > y]]></foo>`)
let result = validateXML(xmlSchema, doc)
t.ok(result.ok, 'text element with CDATA is valid')
t.end()
})

test('XMLSchema: unknown element', t => {
let xmlSchema = _compileRNG(SEQUENCE, withSerialization)
let xmlSchema = _compileRNGString(SEQUENCE, withSerialization)
let doc = DefaultDOMElement.parseXML(`<foo><bar /></foo>`)
let result = validateXML(xmlSchema, doc)
t.notOk(result.ok, 'xml is not valid')
Expand Down Expand Up @@ -188,7 +188,7 @@ function XMLSchemaTests (withSerialization) {
`

test('XMLSchema: zeroOrMore choices', t => {
let xmlSchema = _compileRNG(ZERO_OR_MORE_CHOICES, withSerialization)
let xmlSchema = _compileRNGString(ZERO_OR_MORE_CHOICES, withSerialization)
let result
result = validateXML(xmlSchema, DefaultDOMElement.parseXML(`<foo></foo>`))
t.ok(result.ok, 'xml is valid')
Expand Down Expand Up @@ -225,7 +225,7 @@ function XMLSchemaTests (withSerialization) {
`

test('XMLSchema: sequence', t => {
let xmlSchema = _compileRNG(SEQUENCE, withSerialization)
let xmlSchema = _compileRNGString(SEQUENCE, withSerialization)
let result = validateXML(xmlSchema, DefaultDOMElement.parseXML(`<foo><baz>bla</baz><bar>blupp</bar></foo>`))
t.notOk(result.ok, 'xml is not valid')
t.end()
Expand Down Expand Up @@ -255,7 +255,7 @@ function XMLSchemaTests (withSerialization) {
`

test('XMLSchema: plus', t => {
let xmlSchema = _compileRNG(PLUS, withSerialization)
let xmlSchema = _compileRNGString(PLUS, withSerialization)
let result
result = validateXML(xmlSchema, DefaultDOMElement.parseXML(`<foo><bar></bar></foo>`))
t.ok(result.ok, 'xml is valid')
Expand Down Expand Up @@ -287,7 +287,7 @@ function XMLSchemaTests (withSerialization) {
`

test('XMLSchema: optional', t => {
let xmlSchema = _compileRNG(OPTIONAL, withSerialization)
let xmlSchema = _compileRNGString(OPTIONAL, withSerialization)
let result
result = validateXML(xmlSchema, DefaultDOMElement.parseXML(`<foo><bar /></foo>`))
t.ok(result.ok, 'xml is valid')
Expand Down Expand Up @@ -327,7 +327,7 @@ function XMLSchemaTests (withSerialization) {
`

test('XMLSchema: sequence of optional groups', t => {
let xmlSchema = _compileRNG(SEQUENCE_OF_GROUPS, withSerialization)
let xmlSchema = _compileRNGString(SEQUENCE_OF_GROUPS, withSerialization)
let result
result = validateXML(xmlSchema, DefaultDOMElement.parseXML(`<foo></foo>`))
t.ok(result.ok, 'xml is valid')
Expand Down Expand Up @@ -385,7 +385,7 @@ function XMLSchemaTests (withSerialization) {
`

test('XMLSchema: sequence of optional and required elements', t => {
let xmlSchema = _compileRNG(SEQUENCE_OF_OPTIONAL_AND_REQUIRED_ELEMENTS, withSerialization)
let xmlSchema = _compileRNGString(SEQUENCE_OF_OPTIONAL_AND_REQUIRED_ELEMENTS, withSerialization)
let result
result = validateXML(xmlSchema, DefaultDOMElement.parseXML(`<foo></foo>`))
t.notOk(result.ok, 'xml is not valid')
Expand Down Expand Up @@ -437,11 +437,11 @@ function XMLSchemaTests (withSerialization) {

const TWO_RNGS = {
'./lib/Parent.rng': PARENT_SCHEMA,
'./ChildSchema.rng': CHILD_SCHEMA
'./Child.rng': CHILD_SCHEMA
}

test('XMLSchema: including and extending another RNG', t => {
let xmlSchema = compileRNG(new SimpleVFS(TWO_RNGS), ['.', './lib'], 'ChildSchema.rng')
let xmlSchema = _compileRNG(new SimpleVFS(TWO_RNGS), pathStub, './Child.rng', ['./lib'])
if (withSerialization) {
xmlSchema = _withSerialization(xmlSchema)
}
Expand All @@ -451,6 +451,46 @@ function XMLSchemaTests (withSerialization) {
t.end()
})

const GRAND_CHILD_SCHEMA = `
<grammar>
<include href="Child.rng"/>
<define name="foo_content">
<zeroOrMore>
<choice>
<text />
<ref name="bar" />
<ref name="baz" />
</choice>
</zeroOrMore>
</define>
<define name="baz">
<element name="baz">
<text/>
</element>
</define>
<start>
<ref name="foo"/>
</start>
</grammar>
`

const THREE_RNGS = {
'./lib/Parent.rng': PARENT_SCHEMA,
'./lib/Child.rng': CHILD_SCHEMA,
'./GrandChild.rng': GRAND_CHILD_SCHEMA
}

test('XMLSchema: including nother RNGs recursively', t => {
let xmlSchema = _compileRNG(new SimpleVFS(THREE_RNGS), pathStub, './GrandChild.rng', ['./lib'])
if (withSerialization) {
xmlSchema = _withSerialization(xmlSchema)
}
let doc = DefaultDOMElement.parseXML(`<foo>bla<bar>blupp</bar>bla<baz>blupp</baz>bla</foo>`)
let result = validateXML(xmlSchema, doc)
t.ok(result.ok, 'xml is valid')
t.end()
})

const COMBINING_CHOICES = `
<grammar>
<define name="foo">
Expand Down Expand Up @@ -502,7 +542,7 @@ function XMLSchemaTests (withSerialization) {
`

test('XMLSchema: combining choices', t => {
let xmlSchema = _compileRNG(COMBINING_CHOICES, withSerialization)
let xmlSchema = _compileRNGString(COMBINING_CHOICES, withSerialization)
let result
result = validateXML(xmlSchema, DefaultDOMElement.parseXML(`<foo></foo>`))
t.ok(result.ok, 'xml is valid')
Expand Down Expand Up @@ -550,7 +590,7 @@ function XMLSchemaTests (withSerialization) {
`

test('XMLSchema: re-using groups', t => {
let xmlSchema = _compileRNG(REUSING_GROUPS, withSerialization)
let xmlSchema = _compileRNGString(REUSING_GROUPS, withSerialization)
let result
result = validateXML(xmlSchema, DefaultDOMElement.parseXML(`<foo></foo>`))
t.notOk(result.ok, 'xml is not valid')
Expand Down Expand Up @@ -602,7 +642,7 @@ function XMLSchemaTests (withSerialization) {
`

test('XMLSchema: alternative sequences', t => {
let xmlSchema = _compileRNG(ALTERNATIVE_SUB_SEQUENCES, withSerialization)
let xmlSchema = _compileRNGString(ALTERNATIVE_SUB_SEQUENCES, withSerialization)
let result
result = validateXML(xmlSchema, DefaultDOMElement.parseXML(`<foo></foo>`))
t.notOk(result.ok, 'xml is not valid')
Expand Down Expand Up @@ -632,7 +672,7 @@ function XMLSchemaTests (withSerialization) {
`

test('XMLSchema: empty plus (edge case)', t => {
let xmlSchema = _compileRNG(EMPTY_PLUS, withSerialization)
let xmlSchema = _compileRNGString(EMPTY_PLUS, withSerialization)
let result
result = validateXML(xmlSchema, DefaultDOMElement.parseXML(`<foo></foo>`))
t.ok(result.ok, 'xml is valid')
Expand Down Expand Up @@ -665,8 +705,20 @@ class SimpleVFS {
}
}

function _compileRNG (rngStr, withSerialization) {
let xmlSchema = compileRNG(rngStr)
const pathStub = {
dirname (f) {
let idx = f.lastIndexOf('/')
if (idx >= 0) {
return f.slice(0, idx)
} else {
return f
}
}
}

function _compileRNGString (rngStr, withSerialization) {
let vfs = new SimpleVFS({ './Test.rng': rngStr })
let xmlSchema = _compileRNG(vfs, pathStub, './Test.rng', [])
if (withSerialization) {
xmlSchema = _withSerialization(xmlSchema)
}
Expand Down

0 comments on commit 67431c3

Please sign in to comment.