Best way to create an XML file?

46 views
Skip to first unread message

Tim Smith

unread,
May 5, 2022, 2:58:27 PM5/5/22
to topbrai...@googlegroups.com
Hi, 

I have a need to express an ontology as specified by an XML schema.  This schema is defined by a vendor to represent a class model in their proprietary schema.

Many years ago, I used SWP and the ui:instance view property to assemble HTML pages to drive a wiki straight from the old TopBraid Live product.  I was able to define how each instance should render itself at the class level.  I think the same thing will work here where I define how Node Shapes and Property Shapes should represent themselves in this proprietary XML schema.

Given all the changes to TBC/EDG since I last did this (2011???), what is the current best approach to expressing an RDF/OWL/SHACL structure as an XML schema?  SWP? ADS/Javascript?

Thanks in advance for your input,

Tim

Holger Knublauch

unread,
May 5, 2022, 8:33:45 PM5/5/22
to topbrai...@googlegroups.com

Hi Tim,

unsurprisingly, I would now recommend ADS/JavaScript. I am attaching an example to get you started. Put it into your EDG Studio workspace, edit the file and open its Swagger UI on the Reports tab:

The (minimal) code for this output is:

To generate XSD from an ontology, the idea would be that you traverse all classes (owl.everyClass()) - here I did sh.everyNodeShape() and then recursively write functions that process it class by class, e.g. to create XSD field declarations for all datatype properties etc.

This starting point may give you some ideas. The alternative would be to use SWP in a similar manner, but it the code would be much harder to maintain and you don't have as much flexibility as a full programming language like JavaScript.

Regards,
Holger
--
You received this message because you are subscribed to the Google Groups "TopBraid Suite Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to topbraid-user...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/topbraid-users/CAF0WbnLnGCZM3KT764iDQTB56WPpUJ8JX_wiu9%3DM6xjkETjphg%40mail.gmail.com.
generatexml.ttl

Tim Smith

unread,
May 6, 2022, 7:47:07 PM5/6/22
to topbrai...@googlegroups.com
Hi Holger,

Thank you for the superb example!  I was able to use it as a starting point and have made some good progress.  There is so much capability within ADS that I need to spend some time learning the API.

Have a great weekend!

Tim

Tim Smith

unread,
May 9, 2022, 8:55:10 AM5/9/22
to topbrai...@googlegroups.com
Hi Holger,

I am experiencing some strange behavior when I try to retrieve values of properties of a property shape.

In the script below, Test Case 1 retrieves the label, superclasses and a single property shape for edg:DatabaseTable in the renderNodeShape function.  Then renderPropertyShape is called from within renderNodeShape but the values of the property shape are shown as "undefined", other than names.

In Test Case 2, I call renderPropertyShape directly with the same property shape and the results are returned as expected.  See screenshot below.

To test, open an empty Technical Assets collection or other graph that imports edg:DatabaseTable and paste the script into the script editor.  

I do not understand why calling renderPropertyShape with the same input value returns different results.  Let me know if this question is outside the boundary of a proper use of this forum.  I'm using EDG Studio 7.2 on Windows 10.

Thanks,

Tim


image.png


/**
 * TEST CASE #1:
 * 
 * Get only a single class for testing - edg:DatabaseTable  
 * Render the class by calling renderNodeShape(...)
 * Then render a single property, edg:DatabaseTable-columnOf-inverse,
 * by calling renderPropertyShape() from within renderNodeShape.
 * 
 * RESULT: The properties of the property shape are undefined.
 */

`TEST CASE 1
    ${owl.everyClass().filter(filterOnlyDatabaseTable).map(renderNodeShape).join('\\n')}

TEST CASE 2:
    ${sh.everyPropertyShape().filter(filterToOnlyColumnOf).map(renderPropertyShape)} 
`
/**
 * TEST CASE #2:
 * 
 * Render the same property shape by calling renderPropertyShape() directly from here.
 * Don't render the node shape first.
 * 
 * Get a single property shape by filtering all property shapes down to one 
 * that is a shape of edg:DatabaseTable - edg:DatabaseTable-columnOf-inverse.  
 * Render the shape by calling renderPropertyShape as above.
 * 
 * RESULT: The properties of the property shapes are returned as expected.
 */



/**
 * Produces the XML for a given NodeShape.
 *  
 * For testing, I've filtered the properties to a single property shape so Test Case 1 & 2 are easily compared.
 * 
 * @param {owl_Class} nodeShape
 */
function renderNodeShape(nodeShape) {
    
    return `
    name="${nodeShape.labels[0]}
    parents="${nodeShape.superclasses}">
    ${nodeShape.properties.filter(filterToOnlyColumnOf).map(renderPropertyShape)}
`
}

/**
 * Grab some properties of the shape for testing purposes.
 * @param {sh_PropertyShape} propShape
 */
function renderPropertyShape(propShape) {
    return `
        property Shape = ${graph.localName(propShape.uri)}
        name = ${(propShape.names)}>
        group = ${propShape.group}
        path = ${propShape.path}
        minCount = ${propShape.minCount}`
}


/**
 * Filter down to edg:DatabaseTable.  It's done this way to mimic what the full code must do.
 * The full code will filter to my namespace only.
 * @param {owl_Class} nodeShape
 */
function filterOnlyDatabaseTable (nodeShape) {
    
    return (nodeShape.uri) == "http://edg.topbraid.solutions/model/DatabaseTable"
    
}

/**
 * Filter down to edg:DatabaseTable-columnOf-inverse.  This is just for testing.
 * @param {sh_PropertyShape} propShape
 */
function filterToOnlyColumnOf (propShape) {
    
    
}


Holger Knublauch

unread,
May 9, 2022, 10:24:48 PM5/9/22
to topbrai...@googlegroups.com

Hi Tim,

this is one of the pitfalls of using the auto-generated APIs. The technical issue is that nodeShape.properties returns instances of NamedNode. You can see that when you mouse-over .properties:

and NamedNode doesn't know how to interpret the properties specific to sh_PropertyShape such as .names and .group.

OTOH sh.everyPropertyShape() returns instances of sh_PropertyShape

To avoid this, you could potentially avoid using the special subclasses of NamedNode and use the generic value functions. For example use

function renderPropertyShape(propShape) {
    return `
        property Shape = ${graph.localName(propShape.uri)}
        name = ${(propShape.value(sh.name))}>
        group = ${propShape.value(sh.group)}
        path = ${propShape.value(sh.path)}
        minCount = ${propShape.value(sh.minCount)}`
}
this will work fine on any instance of NamedNode but the code is a bit verbose.

Alternatively, just type-cast the object that you are getting in the renderPropertyShape function to make sure it has the expected type:

function renderPropertyShape(propShape) {
    propShape = sh.asPropertyShape(propShape); // type-cast
    return `
        property Shape = ${graph.localName(propShape.uri)}
        name = ${(propShape.names)}>
        group = ${propShape.group}
        path = ${propShape.path}
        minCount = ${propShape.minCount}`
}

An alternative syntax would be to cast the whole array using sh.asPropertyShapeArray:

${sh.asPropertyShapeArray(nodeShape.properties).
filter(filterToOnlyColumnOf).
map(renderPropertyShape)}


The follow-up question is of course, why does sh_NodeShape.properties not return an array of sh_PropertyShape. This is not very user-friendly. The reason is that sh:property doesn't declare a sh:class (or sh:node) and therefore the API generator doesn't know that it can safely type-cast the values of sh:property itself. I have created a ticket for myself to get this improved for 7.3 to match users' expectations better. I'll probably just hard-code that rule because it's tricky to mess around with the SHACL metaclasses.

Meanwhile you have two or three work-arounds above.

BTW modern JS versions like the one in ADS support these lambda-functions as an alternative to introducing explicit named functions. For example:

    ${owl.everyClass().
        filter(nodeShape => nodeShape.uri == "http://edg.topbraid.solutions/model/DatabaseTable").
        map(renderNodeShape).join('\\n')}
where the filter function is like (value) => expression

Holger

Tim Smith

unread,
May 11, 2022, 5:46:06 PM5/11/22
to topbrai...@googlegroups.com
Hi Holger,

Thank you for the explanation.  I had not thought of checking the function signatures.  Now the observed behavior makes sense.  I will cast the returned values to make it work.

I did not know about lamba-functions.  That looks really interesting!

Thanks again!

Tim

Reply all
Reply to author
Forward
0 new messages