Dear Andy, Thanks again for your suggesting. Some comments in line.
On 13 Feb 2016, at 17:04, Andy Seaborne <[email protected]<mailto:[email protected]>> wrote: On 04/02/16 19:27, Carlo.Allocca wrote: Dear Andy and All, Thank you very much for all your suggestions and willingness. I went through all of them again and, assembling them (changing a bit the code reported in the previous email) I got a reasonable first version that is working. But I had to use arg0 instead of arg1 as suggested. This will get you into trouble! It probably means something somewhere below is modifying the input structure, not taking a copy. I will double check and come back to this. Please, could I ask the followings: Is there any way to access the aggregate expressions (e.g. GROUP BY or HAVING ) when applying an implementation of ElementTransform? Look at QueryTransformOps.transform(Query query, ..) which applies transaforms to the thigns that attach to the query object itseld like GROUP. Thanks. I will try this one and see how to combine with the transform that I have implemented. Many Thanks, Best Regards, Carlo The reason of such a question is: when applying the operation remove of the triple (?x2 foaf:mbox2 ?mbox2 .) over Q1, it would make sense to eliminate ORDER BY too as it contains a variable (?x2) from the triple that was eliminated and there no other triple containing such a variable. Q1: String qString8 = SELECT DISTINCT ?x2 ?mbox2 where " { ?x foaf:name ?name . ?x2 foaf:mbox2 ?mbox2 . } ORDER BY ?x2 Many Thanks in advance. Best Regards, Carlo On 3 Feb 2016, at 14:55, Carlo.Allocca <[email protected]<mailto:[email protected]><mailto:[email protected]>> wrote: Dear Andy and All, sorry for this long thread. I am sure I was not able to put in practice some of your suggestions. I put the full code here http://collabedit.com/wfhtq and reported below. I tested it over the Q1 with the triple (?boss ex:isBossOf ?ind). =========== BEFORE Q1: PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> PREFIX ex: <http://www.semanticweb.org/dataset1/> PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> SELECT DISTINCT ?ind ?boss ?g WHERE { { ?ind rdf:type ?z } UNION { ?boss ex:isBossOf ?ind FILTER ( ?boss = "mathieu" ) } } ============= AFTER Q1: PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> PREFIX ex: <http://www.semanticweb.org/dataset1/> PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> SELECT DISTINCT ?ind ?boss ?g WHERE { { ?ind rdf:type ?z } UNION { # Empty BGP FILTER ( ?boss = "mathieu" ) } } But the filter is still there even if when I trace the execution it removes it. I am doing the update between the AST and remove of the filter in @Override public Element transform(ElementGroup arg0, List<Element> arg1) { Iterator<Element> itr = arg1.iterator(); while (itr.hasNext()) { Element elem = itr.next(); … ... } //With this code I am saying the following: make effective all the modifications that //have been made so far, if any. if (arg0.getElements() == arg1) { return arg0; } else { ElementGroup el2 = new ElementGroup(); el2.getElements().addAll(arg1); return el2; } } What am I doing wrong? Many Thanks for your help. Best Regards, Carlo === Code //The main Class public class RemoveTriple { public RemoveTriple(){ super(); } public Query removeTP(Query q, Triple tp) { RemoveOpTransform rOpTransform = new RemoveOpTransform(q,tp); Query queryWithoutTriplePattern = QueryTransformOps.transform(q, rOpTransform) ; return queryWithoutTriplePattern; } } // The current implementation of the RemoveOpTransform class. The class that implements the removal of the triple public class RemoveOpTransform implements ElementTransform { private Query query; private Triple triple; public RemoveOpTransform(Query q, Triple tp) { this.query = q; this.triple = tp; } @Override public Element transform(ElementTriplesBlock arg0) { System.out.println("[RemoveOpTransform::transform(ElementTriplesBlock arg0)] " + arg0.toString()); System.out.println(""); return arg0; } // This is the code related to the @Override public Element transform(ElementPathBlock eltPB) { if (eltPB.isEmpty()) { return eltPB; } Iterator<TriplePath> l = eltPB.patternElts(); while (l.hasNext()) { TriplePath tp = l.next(); if (tp.asTriple().matches(this.triple)) { l.remove(); return this.transform(eltPB);//eltPB; } } return eltPB; } @Override public Element transform(ElementGroup arg0, List<Element> arg1) { Iterator<Element> itr = arg1.iterator(); while (itr.hasNext()) { Element elem = itr.next(); // I should go one by one the and examinate all the possible cases. For example: //UNNION if (elem instanceof ElementUnion) { if (isUnionBothSidesEmpty) { itr.remove(); } } //OPTION if (elem instanceof ElementOptional) { boolean isElementOptionalEmpty = isElementOptionalEmpty((ElementOptional) elem); if (isElementOptionalEmpty) { itr.remove(); // ElementGroup el2 = new ElementGroup(); // el2.getElements().addAll(arg1); // return el2; } } //ELEMENTGROUP if (elem instanceof ElementGroup) { boolean isElementGroupEmpty = isElementGroupEmpty((ElementGroup) elem); if (isElementGroupEmpty) { itr.remove(); // ElementGroup el2 = new ElementGroup(); // el2.getElements().addAll(arg1); // return el2; } } //FILTER if (elem instanceof ElementFilter) { //... check if this filter is the one that we should remove //...get the variables of the triple pattern that we want to delete Set<Var> tpVars = new HashSet(); Node subj = this.triple.getSubject(); if (subj.isVariable()) { tpVars.add((Var) subj); } Node pred = this.triple.getPredicate(); if (pred.isVariable()) { tpVars.add((Var) pred); } Node obj = this.triple.getObject(); if (obj.isVariable()) { tpVars.add((Var) obj); } //...get the variables of the FILTER expression Set<Var> expVars = ((ElementFilter) elem).getExpr().getVarsMentioned(); //...check whether the FILTER expression contains any of the triple pattern variable for (Var var : expVars) { //...if it does then we can delete the entire FILTER expression if (tpVars.contains(var)) { itr.remove(); break; } } } }// End of While //With this code I am saying the following: make effective all the modifications that //have been made so far, if any. if (arg0.getElements() == arg1) { return arg0; } else { ElementGroup el2 = new ElementGroup(); el2.getElements().addAll(arg1); return el2; } } @Override public Element transform(ElementFilter arg0, Expr arg1) { return arg0; } @Override public Element transform(ElementAssign arg0, Var arg1, Expr arg2) { return arg0; } @Override public Element transform(ElementBind arg0, Var arg1, Expr arg2) { System.out.println("[RemoveOpTransform::transform(ElementBind arg0)] " + arg0.toString()); System.out.println(""); return arg0; } @Override public Element transform(ElementData arg0) { System.out.println("[RemoveOpTransform::transform(ElementData arg0)] " + arg0.toString()); System.out.println(""); return arg0; } @Override public Element transform(ElementDataset arg0, Element arg1) { System.out.println("[RemoveOpTransform::transform(ElementDataset arg0)] " + arg0.toString()); System.out.println(""); return arg0; } @Override public Element transform(ElementUnion arg0, List<Element> arg1) { System.out.println("[RemoveOpTransform::transform(ElementUnion arg0)] arg0 " + arg0.toString()); System.out.println("[RemoveOpTransform::transform(ElementUnion arg0)] arg1 " + arg1.toString()); System.out.println(""); return arg0; } @Override public Element transform(ElementOptional arg0, Element arg1) { return arg0; } @Override public Element transform(ElementNamedGraph arg0, Node arg1, Element arg2) { System.out.println("[RemoveOpTransform::transform(ElementNamedGraph arg0)] "); System.out.println(""); return arg0; } @Override public Element transform(ElementExists arg0, Element arg1) { System.out.println("[RemoveOpTransform::transform(ElementExists arg0)] "); System.out.println(""); return arg0; } @Override public Element transform(ElementNotExists arg0, Element arg1) { System.out.println("[RemoveOpTransform::transform(ElementNotExists arg0)] "); System.out.println(""); return arg0; } @Override public Element transform(ElementMinus arg0, Element arg1) { System.out.println("[RemoveOpTransform::transform(ElementMinus arg0)] "); System.out.println(""); return arg0; } @Override public Element transform(ElementService arg0, Node arg1, Element arg2) { System.out.println("[RemoveOpTransform::transform(ElementService arg0)] "); System.out.println(""); return arg0; } @Override public Element transform(ElementSubQuery arg0, Query arg1) { System.out.println("[RemoveOpTransform::transform(ElementSubQuery arg0)] "); System.out.println(""); return arg0; } private boolean isUnionBothSidesEmpty1(ElementUnion elementUnion) { List<Element> elemListUnion = elementUnion.getElements();//.getElements(); if (elemListUnion.size() == 2) { Element left = elemListUnion.get(0); Element right = elemListUnion.get(1); if (left instanceof ElementGroup && right instanceof ElementGroup) { if ((((ElementGroup) left).getElements().size() == 1) && (((ElementGroup) right).getElements().size() == 1)) { Element elLeft = ((ElementGroup) left).getElements().get(0); Element elRight = ((ElementGroup) right).getElements().get(0); if ((elLeft instanceof ElementPathBlock) && (elRight instanceof ElementPathBlock)) { ElementPathBlock epbLeft = (ElementPathBlock) elLeft; ElementPathBlock epbRight = (ElementPathBlock) elRight; if (epbLeft.isEmpty() && epbRight.isEmpty()) { return true; } } } } } return false; } private boolean isElementGroupEmpty(ElementGroup elementGroup) { List<Element> elList = ((ElementGroup) elementGroup).getElements(); if (elList.size() == 1) { Element el = (Element) elList.get(0); if (el instanceof ElementPathBlock) { ElementPathBlock tmp = (ElementPathBlock) el; if (tmp.isEmpty()) { return true; } } } return false; } private boolean isElementOptionalEmpty(ElementOptional elementOptional) { Element elm = ((ElementOptional) elementOptional).getOptionalElement();//.getElements(); if (elm instanceof ElementGroup) { List<Element> elList = ((ElementGroup) elm).getElements(); if (elList.size() == 1) { Element el = (Element) elList.get(0); if (el instanceof ElementPathBlock) { ElementPathBlock tmp = (ElementPathBlock) el; if (tmp.isEmpty()) { return true; } } } } return false; } } On 3 Feb 2016, at 00:00, Carlo.Allocca <[email protected]<mailto:[email protected]><mailto:[email protected]><mailto:[email protected]>> wrote: Dear Andy and Paul, some comments follow in lines. On 2 Feb 2016, at 22:33, Andy Seaborne <[email protected]<mailto:[email protected]><mailto:[email protected]><mailto:[email protected]>> wrote: On 02/02/16 19:29, Paul Houle wrote: Carlo, Andy, I like the Iterator<> interfaces in the Jena framework for getting data out, but I make a habit of always putting results in a List or Queue or something before putting them back into the same Jena model because i get less BS per mile that way in terms of Exceptions and other exceptional events. Does Jena have an official policy on being reenterable in that way? Carlo's issues are nothing to do with iterator policy. Carlo - Use arg1 else you will not see your changes so far. In public Element transform(ElementGroup arg0, List<Element> arg1) arg0 is the element from the AST before modification and arg1 is the new elements to go in after modification by lower levels of the bottom-up rewrite. So if you rewrite a ElementFilter by making a new one, it will appear in arg1 not in arg0. I see. Thank you. Based on this, I am going to use "Iterator<Element> itr = arg1.iterator(); when implementing public Element transform(ElementGroup arg0, List<Element> arg1). Do not modify the Element* arguments in place. See ElementTransformCopyBase The default implementation is: @Override public Element transform(ElementGroup el, List<Element> elts) { if ( el.getElements() == elts ) return el ; ElementGroup el2 = new ElementGroup() ; el2.getElements().addAll(elts) ; return el2 ; } i.e if any change, detected by being not the exact identical list, then do a copy of the structure. This saves object churn. I am going to change my implementation in such a way include all the above suggestions and clarifications. Many Thanks for your willingness. Best Regards, Carlo. Andy On Tue, Feb 2, 2016 at 2:13 PM, Carlo.Allocca <[email protected]<mailto:[email protected]><mailto:[email protected]><mailto:[email protected]>> wrote: Dear Andy and All, while I was extending and testing the code that I wrote so far concerning the removing a triple from a given SPARQL query, I realised that I get different outputs depending on how I start the implementation of the public Element transform(ElementGroup arg0, List<Element> arg1). In particular, if I start with (1) I obtain some results, if I start with (2) I obtain something different (you can see below the details). I have also used ElementTransformCleanGroupsOfOne when ElementGroup is empty ElementTransform transform = new ElementTransformCleanGroupsOfOne(); Element el2 = ElementTransformer.transform(eg, transform); return el2; but no difference in results. I am sure I am doing something wrong. Moreover, my questions are: what is the main difference between the two approaches? and when I should use ElementGroup arg0 and when List<Element> arg1? (1) public Element transform(ElementGroup arg0, List<Element> arg1) { List<Element> elemList = arg0.getElements(); Iterator<Element> itr = elemList.iterator(); while (itr.hasNext()) { } … … } (2) public Element transform(ElementGroup arg0, List<Element> arg1) { Iterator<Element> itr = arg1.iterator(); while (itr.hasNext()) { } … … } I know that it may be related to the little knowledge about Jena. Many Thanks in advice for your clarification on the above. Best Regards, Carlo ======= Below, I reported the used code (at very bottom), the two used scenario with test-cases and results. In practice, you can notice that: ==== TESTING: Scenario A: public Element transform(ElementGroup arg0, List<Element> arg1) { List<Element> elemList = arg0.getElements(); Iterator<Element> itr = elemList.iterator(); while (itr.hasNext()) { } … … } Test 1: The triple to remove is (?x foaf:mbox ?mbox ) using the below query Q1: =========== BEFORE Q1 PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> PREFIX foaf: <http://xmlns.com/foaf/0.1/> SELECT DISTINCT ?name ?mbox WHERE { ?x foaf:name ?name OPTIONAL { ?x foaf:mbox ?mbox } } ============= AFTER Q1 PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> PREFIX foaf: <http://xmlns.com/foaf/0.1/> SELECT DISTINCT ?name ?mbox WHERE { ?x foaf:name ?name } Test2: The triple to remove is (?boss1 ex:isBossOf1 ?ind ) using the below query Q2: =========== BEFORE Q2 PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> PREFIX ex: <http://www.semanticweb.org/dataset1/> PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> SELECT DISTINCT ?ind ?boss ?g WHERE { { ?ind rdf:type ?z OPTIONAL { ?boss1 ex:isBossOf1 ?ind } } UNION { { ?boss ex:isBossOf1 ?ind } UNION { ?boss ex:isBossOf ?ind FILTER ( ?boss = "mathieu" ) } } } ============= AFTER Q2: it does not remove the triple. PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> PREFIX ex: <http://www.semanticweb.org/dataset1/> PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> SELECT DISTINCT ?ind ?boss ?g WHERE { { ?ind rdf:type ?z } UNION { { ?boss ex:isBossOf1 ?ind } UNION { ?boss ex:isBossOf ?ind FILTER ( ?boss = "mathieu" ) } } } Test 3: The triple to remove is (?ind rdf:type ?z) using the below query Q3: =========== BEFORE Q3: PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> PREFIX ex: <http://www.semanticweb.org/dataset1/> PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> SELECT DISTINCT ?ind ?boss ?g WHERE { ?ind rdf:type ?z FILTER ( ?ind = "mathieu" ) } ============= AFTER Q3: There is still an empty BGP present. PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> PREFIX ex: <http://www.semanticweb.org/dataset1/> PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> SELECT DISTINCT ?ind ?boss ?g WHERE { # Empty BGP } Scenario B: public Element transform(ElementGroup arg0, List<Element> arg1) { Iterator<Element> itr = arg1.iterator(); while (itr.hasNext()) { } … … } Test 1: The triple to remove is (?x foaf:mbox ?mbox ) using the below query Q1: =========== BEFORE Q1 PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> PREFIX foaf: <http://xmlns.com/foaf/0.1/> SELECT DISTINCT ?name ?mbox WHERE { ?x foaf:name ?name OPTIONAL { ?x foaf:mbox ?mbox } } ============= AFTER Q1: there is still the OPTION (with a ElementGroup empty) clause. PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> PREFIX foaf: <http://xmlns.com/foaf/0.1/> SELECT DISTINCT ?name ?mbox WHERE { ?x foaf:name ?name OPTIONAL { # Empty BGP } } Test 2: The triple to remove is (?boss1 ex:isBossOf1 ?ind ) using the below query Q2: =========== BEFORE Q2 PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> PREFIX ex: <http://www.semanticweb.org/dataset1/> PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> SELECT DISTINCT ?ind ?boss ?g WHERE { { ?ind rdf:type ?z OPTIONAL { ?boss1 ex:isBossOf1 ?ind } } UNION { { ?boss ex:isBossOf1 ?ind } UNION { ?boss ex:isBossOf ?ind FILTER ( ?boss = "mathieu" ) } } } ============= AFTER Q2: it does not remove the OPTION and it leaves an empty BGP. PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> PREFIX ex: <http://www.semanticweb.org/dataset1/> PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> SELECT DISTINCT ?ind ?boss ?g WHERE { { ?ind rdf:type ?z OPTIONAL { # Empty BGP } } UNION { { ?boss ex:isBossOf1 ?ind } UNION { ?boss ex:isBossOf ?ind FILTER ( ?boss = "mathieu" ) } } } Test 3: The triple to remove is (?ind rdf:type ?z) using the below query Q3: =========== BEFORE Q3 PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> PREFIX ex: <http://www.semanticweb.org/dataset1/> PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> SELECT DISTINCT ?ind ?boss ?g WHERE { ?ind rdf:type ?z FILTER ( ?ind = "mathieu" ) } ============= AFTER Q3: It does not remove the FILTER, but just the triple. PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> PREFIX ex: <http://www.semanticweb.org/dataset1/> PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> SELECT DISTINCT ?ind ?boss ?g WHERE { { ?ind rdf:type ?z } UNION { # Empty BGP FILTER ( ?boss = "mathieu" ) } } === FULL CODE used with public Element transform(ElementPathBlock eltPB) @Override public Element transform(ElementPathBlock eltPB) { if (eltPB.isEmpty()) { //System.out.println("[RemoveOpTransform::transform(ElementPathBlock arg0)] ElementPathBlock IS EMPTY:: " + eltPB.toString()); return eltPB; } System.out.println("[RemoveOpTransform::transform(ElementPathBlock arg0)] ElementPathBlock:: " + eltPB.toString()); Iterator<TriplePath> l = eltPB.patternElts(); while (l.hasNext()) { TriplePath tp = l.next(); if (tp.asTriple().matches(this.triple)) { l.remove(); System.out.println("[RemoveOpTransform::transform(ElementPathBlock arg0)] ElementPathBlock:: " + tp.toString() + " TRIPLE JUST REMOVED!!!"); //System.out.println("[RemoveOpTransform::transform(ElementPathBlock arg0)] TRIPLE JUST REMOVED!!! "); System.out.println(""); return this.transform(eltPB);//eltPB; } } return eltPB; } === FULL CODE used with public Element transform(ElementGroup arg0, List<Element> arg1) @Override public Element transform(ElementGroup arg0, List<Element> arg1) { List<Element> elemList = arg0.getElements(); Iterator<Element> itr = elemList.iterator(); //Iterator<Element> itr = arg1.iterator(); while (itr.hasNext()) { Element elem = itr.next(); if (elem instanceof ElementOptional) { boolean isElementOptionalEmpty = isElementOptionalEmpty((ElementOptional) elem); if (isElementOptionalEmpty) { itr.remove(); } } else if (elem instanceof ElementGroup) { boolean isElementGroupEmpty = isElementGroupEmpty((ElementGroup) elem); if (isElementGroupEmpty) { itr.remove(); } } else if (elem instanceof ElementFilter) { //... check if this filter is the one that we should remove //...get the variables of the triple pattern that we want to delete Set<Var> tpVars = new HashSet(); Node subj = this.triple.getSubject(); if (subj.isVariable()) { tpVars.add((Var) subj); } Node pred = this.triple.getPredicate(); if (pred.isVariable()) { tpVars.add((Var) pred); } Node obj = this.triple.getObject(); if (obj.isVariable()) { tpVars.add((Var) obj); } //...get the variables of the FILTER expression Set<Var> expVars = ((ElementFilter) elem).getExpr().getVarsMentioned(); //...check whether the FILTER expression contains any of the triple pattern variable for (Var var : expVars) { //..if it does then we have to delete the entire FILTER expression if (tpVars.contains(var)) { itr.remove(); } } } else if (elem instanceof ElementUnion) { boolean isUnionBothSidesEmpty = isUnionBothSidesEmpty1((ElementUnion) elem); if (isUnionBothSidesEmpty) { itr.remove(); } } } return arg0; } On 2 Feb 2016, at 10:54, Carlo.Allocca <[email protected]<mailto:[email protected]><mailto:[email protected]><mailto:[email protected]><mailto: [email protected]<mailto:[email protected]><mailto:[email protected]><mailto:[email protected]>>> wrote: Dear Andy, Thank you for your time. Very appreciated. Some comments follow in lines. On 2 Feb 2016, at 09:36, Andy Seaborne <[email protected]<mailto:[email protected]><mailto:[email protected]><mailto:[email protected]><mailto: [email protected]<mailto:[email protected]><mailto:[email protected]><mailto:[email protected]>>> wrote: when removing the triple (?boss ex:isBossOf ?ind .”), I get SELECT DISTINCT ?ind ?boss ?g WHERE { { ?ind rdf:type ?z } UNION { { ?boss ex:isBossOf1 ?ind } UNION { # Empty BGP } } } which is OK. I just need to find out how to remove an ElementGroup which contains only one element which is the EMPTY one. Of course, I need to do the same for the other case, e.g. OPTION, SUBquery, etc. Do note that evaluating {} (empty syntax group) yields one row of zero columns - it contributes to the overall results (it's the join identity). I see. To avoid this I am going to apply a ElementTransformCleanGroupsOfOne as you suggested. Now you have to look at all the elements that have a group in ElementUnion, ElementOptional, ElementMinus, … Yes, I need to cover all the SPARQL language from the “public Element transform(ElementGroup arg0, List<Element> arg1)” call. At least this is my understanding so far. That is what ElementTransformCleanGroupsOfOne does, except it looks for "groups of one" .. UNION { { stuff } } and isn't to fussy about finding them all (it's an optimization, more a tidying of the tree, not a change in the effect of a query which is what removing triple patterns is). And of course changes from the bottom could potentially cause change all the way up to the top of the syntax tree. also: they maybe be original, legal empty groups in the tree. Thanks for the detailed clarifications. Indeed, I will consider them. Many Thanks, Best Regards, Carlo Andy -- The Open University is incorporated by Royal Charter (RC 000391), an exempt charity in England & Wales and a charity registered in Scotland (SC 038302). The Open University is authorised and regulated by the Financial Conduct Authority. -- The Open University is incorporated by Royal Charter (RC 000391), an exempt charity in England & Wales and a charity registered in Scotland (SC 038302). The Open University is authorised and regulated by the Financial Conduct Authority.
