diff --git a/RDFSharp.Test/Model/RDFGraphTest.cs b/RDFSharp.Test/Model/RDFGraphTest.cs
index 40b3830c..abb5f9f0 100644
--- a/RDFSharp.Test/Model/RDFGraphTest.cs
+++ b/RDFSharp.Test/Model/RDFGraphTest.cs
@@ -1584,8 +1584,8 @@ public void ShouldImportFromFileWithEnabledDatatypeDiscovery(string fileExtensio
.AddTriple(triple2)
.AddDatatype(new RDFDatatype(new Uri($"ex:mydt{(int)format}"), RDFModelEnums.RDFDatatypes.XSD_STRING, [
new RDFPatternFacet("^ex$") ]));
- graph1.ToFile(format, Path.Combine(Environment.CurrentDirectory, $"RDFGraphTest_ShouldImportFromFile{fileExtension}"));
- RDFGraph graph2 = RDFGraph.FromFile(format, Path.Combine(Environment.CurrentDirectory, $"RDFGraphTest_ShouldImportFromFile{fileExtension}"), true);
+ graph1.ToFile(format, Path.Combine(Environment.CurrentDirectory, $"RDFGraphTest_ShouldImportFromFile{fileExtension}WithEnabledDatatypeDiscovery"));
+ RDFGraph graph2 = RDFGraph.FromFile(format, Path.Combine(Environment.CurrentDirectory, $"RDFGraphTest_ShouldImportFromFile{fileExtension}WithEnabledDatatypeDiscovery"), true);
Assert.IsNotNull(graph2);
Assert.IsTrue(graph2.TriplesCount == 9);
diff --git a/RDFSharp.Test/Store/Engines/RDFMemoryStoreTest.cs b/RDFSharp.Test/Store/Engines/RDFMemoryStoreTest.cs
index 9757f3c2..32f41a59 100644
--- a/RDFSharp.Test/Store/Engines/RDFMemoryStoreTest.cs
+++ b/RDFSharp.Test/Store/Engines/RDFMemoryStoreTest.cs
@@ -1141,6 +1141,31 @@ public void ShouldImportFromFile(string fileExtension, RDFStoreEnums.RDFFormats
Assert.IsTrue(store2.Equals(store1));
}
+ [DataTestMethod]
+ [DataRow(".nq", RDFStoreEnums.RDFFormats.NQuads)]
+ [DataRow(".trix", RDFStoreEnums.RDFFormats.TriX)]
+ [DataRow(".trig", RDFStoreEnums.RDFFormats.TriG)]
+ public void ShouldImportFromFileWithEnabledDatatypeDiscovery(string fileExtension, RDFStoreEnums.RDFFormats format)
+ {
+ RDFMemoryStore store1 = new RDFMemoryStore();
+ RDFQuadruple quadruple1 = new RDFQuadruple(new RDFContext("http://ex/ctx/"), new RDFResource("http://ex/subj/"), new RDFResource("http://ex/pred/"), new RDFPlainLiteral("lit", "en-US"));
+ RDFQuadruple quadruple2 = new RDFQuadruple(new RDFContext("http://ex/ctx/"), new RDFResource("http://ex/subj/"), new RDFResource("http://ex/pred/"), new RDFResource("http://ex/obj/"));
+ store1.AddQuadruple(quadruple1)
+ .AddQuadruple(quadruple2)
+ .MergeGraph(new RDFGraph()
+ .AddDatatype(new RDFDatatype(new Uri($"ex:mydtPP{(int)format}"), RDFModelEnums.RDFDatatypes.XSD_STRING, [
+ new RDFPatternFacet("^ex$") ])));
+ store1.ToFile(format, Path.Combine(Environment.CurrentDirectory, $"RDFMemoryStoreTest_ShouldImportFromFile{fileExtension}WithEnabledDatatypeDiscovery"));
+ RDFMemoryStore store2 = RDFMemoryStore.FromFile(format, Path.Combine(Environment.CurrentDirectory, $"RDFMemoryStoreTest_ShouldImportFromFile{fileExtension}WithEnabledDatatypeDiscovery"), true);
+
+ Assert.IsNotNull(store2);
+ Assert.IsTrue(store2.QuadruplesCount == 9);
+ Assert.IsTrue(store2.Equals(store1));
+ //Test that automatic datatype discovery happened successfully
+ Assert.IsTrue(RDFDatatypeRegister.GetDatatype($"ex:mydtPP{(int)format}").TargetDatatype == RDFModelEnums.RDFDatatypes.XSD_STRING);
+ Assert.IsTrue(RDFDatatypeRegister.GetDatatype($"ex:mydtPP{(int)format}").Facets.Single() is RDFPatternFacet fct && fct.Pattern == "^ex$");
+ }
+
[DataTestMethod]
[DataRow(".nq", RDFStoreEnums.RDFFormats.NQuads)]
[DataRow(".trix", RDFStoreEnums.RDFFormats.TriX)]
@@ -1174,14 +1199,39 @@ public async Task ShouldImportFromFileAsync(string fileExtension, RDFStoreEnums.
RDFQuadruple quadruple1 = new RDFQuadruple(new RDFContext("http://ex/ctx/"), new RDFResource("http://ex/subj/"), new RDFResource("http://ex/pred/"), new RDFPlainLiteral("lit", "en-US"));
RDFQuadruple quadruple2 = new RDFQuadruple(new RDFContext("http://ex/ctx/"), new RDFResource("http://ex/subj/"), new RDFResource("http://ex/pred/"), new RDFResource("http://ex/obj/"));
store1.AddQuadruple(quadruple1).AddQuadruple(quadruple2);
- store1.ToFile(format, Path.Combine(Environment.CurrentDirectory, $"RDFMemoryStoreTest_ShouldImportFromFile{fileExtension}"));
- RDFMemoryStore store2 = await RDFMemoryStore.FromFileAsync(format, Path.Combine(Environment.CurrentDirectory, $"RDFMemoryStoreTest_ShouldImportFromFile{fileExtension}"));
+ store1.ToFile(format, Path.Combine(Environment.CurrentDirectory, $"RDFMemoryStoreTest_ShouldImportFromFileAsync{fileExtension}"));
+ RDFMemoryStore store2 = await RDFMemoryStore.FromFileAsync(format, Path.Combine(Environment.CurrentDirectory, $"RDFMemoryStoreTest_ShouldImportFromFileAsync{fileExtension}"));
Assert.IsNotNull(store2);
Assert.IsTrue(store2.QuadruplesCount == 2);
Assert.IsTrue(store2.Equals(store1));
}
+ [DataTestMethod]
+ [DataRow(".nq", RDFStoreEnums.RDFFormats.NQuads)]
+ [DataRow(".trix", RDFStoreEnums.RDFFormats.TriX)]
+ [DataRow(".trig", RDFStoreEnums.RDFFormats.TriG)]
+ public async Task ShouldImportFromFileAsyncWithEnabledDatatypeDiscovery(string fileExtension, RDFStoreEnums.RDFFormats format)
+ {
+ RDFMemoryStore store1 = new RDFMemoryStore();
+ RDFQuadruple quadruple1 = new RDFQuadruple(new RDFContext("http://ex/ctx/"), new RDFResource("http://ex/subj/"), new RDFResource("http://ex/pred/"), new RDFPlainLiteral("lit", "en-US"));
+ RDFQuadruple quadruple2 = new RDFQuadruple(new RDFContext("http://ex/ctx/"), new RDFResource("http://ex/subj/"), new RDFResource("http://ex/pred/"), new RDFResource("http://ex/obj/"));
+ store1.AddQuadruple(quadruple1)
+ .AddQuadruple(quadruple2)
+ .MergeGraph(new RDFGraph()
+ .AddDatatype(new RDFDatatype(new Uri($"ex:mydtP{(int)format}"), RDFModelEnums.RDFDatatypes.XSD_STRING, [
+ new RDFPatternFacet("^ex$") ])));
+ store1.ToFile(format, Path.Combine(Environment.CurrentDirectory, $"RDFMemoryStoreTest_ShouldImportFromFileAsync{fileExtension}WithEnabledDatatypeDiscovery"));
+ RDFMemoryStore store2 = await RDFMemoryStore.FromFileAsync(format, Path.Combine(Environment.CurrentDirectory, $"RDFMemoryStoreTest_ShouldImportFromFileAsync{fileExtension}WithEnabledDatatypeDiscovery"), true);
+
+ Assert.IsNotNull(store2);
+ Assert.IsTrue(store2.QuadruplesCount == 9);
+ Assert.IsTrue(store2.Equals(store1));
+ //Test that automatic datatype discovery happened successfully
+ Assert.IsTrue(RDFDatatypeRegister.GetDatatype($"ex:mydtP{(int)format}").TargetDatatype == RDFModelEnums.RDFDatatypes.XSD_STRING);
+ Assert.IsTrue(RDFDatatypeRegister.GetDatatype($"ex:mydtP{(int)format}").Facets.Single() is RDFPatternFacet fct && fct.Pattern == "^ex$");
+ }
+
[DataTestMethod]
[DataRow(".nq", RDFStoreEnums.RDFFormats.NQuads)]
[DataRow(".trix", RDFStoreEnums.RDFFormats.TriX)]
@@ -1189,8 +1239,8 @@ public async Task ShouldImportFromFileAsync(string fileExtension, RDFStoreEnums.
public async Task ShouldImportEmptyFromFileAsync(string fileExtension, RDFStoreEnums.RDFFormats format)
{
RDFMemoryStore store1 = new RDFMemoryStore();
- store1.ToFile(format, Path.Combine(Environment.CurrentDirectory, $"RDFMemoryStoreTest_ShouldImportEmptyFromFile{fileExtension}"));
- RDFMemoryStore store2 = await RDFMemoryStore.FromFileAsync(format, Path.Combine(Environment.CurrentDirectory, $"RDFMemoryStoreTest_ShouldImportEmptyFromFile{fileExtension}"));
+ store1.ToFile(format, Path.Combine(Environment.CurrentDirectory, $"RDFMemoryStoreTest_ShouldImportEmptyFromFileAsync{fileExtension}"));
+ RDFMemoryStore store2 = await RDFMemoryStore.FromFileAsync(format, Path.Combine(Environment.CurrentDirectory, $"RDFMemoryStoreTest_ShouldImportEmptyFromFileAsync{fileExtension}"));
Assert.IsNotNull(store2);
Assert.IsTrue(store2.QuadruplesCount == 0);
@@ -1224,6 +1274,32 @@ public void ShouldImportFromStream(RDFStoreEnums.RDFFormats format)
Assert.IsTrue(store2.Equals(store1));
}
+ [DataTestMethod]
+ [DataRow(RDFStoreEnums.RDFFormats.NQuads)]
+ [DataRow(RDFStoreEnums.RDFFormats.TriX)]
+ [DataRow(RDFStoreEnums.RDFFormats.TriG)]
+ public void ShouldImportFromStreamWithEnabledDatatypeDiscovery(RDFStoreEnums.RDFFormats format)
+ {
+ MemoryStream stream = new MemoryStream();
+ RDFMemoryStore store1 = new RDFMemoryStore();
+ RDFQuadruple quadruple1 = new RDFQuadruple(new RDFContext("http://ex/ctx/"), new RDFResource("http://ex/subj/"), new RDFResource("http://ex/pred/"), new RDFPlainLiteral("lit", "en-US"));
+ RDFQuadruple quadruple2 = new RDFQuadruple(new RDFContext("http://ex/ctx/"), new RDFResource("http://ex/subj/"), new RDFResource("http://ex/pred/"), new RDFResource("http://ex/obj/"));
+ store1.AddQuadruple(quadruple1)
+ .AddQuadruple(quadruple2)
+ .MergeGraph(new RDFGraph()
+ .AddDatatype(new RDFDatatype(new Uri($"ex:mydtQQ{(int)format}"), RDFModelEnums.RDFDatatypes.XSD_STRING, [
+ new RDFPatternFacet("^ex$") ])));
+ store1.ToStream(format, stream);
+ RDFMemoryStore store2 = RDFMemoryStore.FromStream(format, new MemoryStream(stream.ToArray()), true);
+
+ Assert.IsNotNull(store2);
+ Assert.IsTrue(store2.QuadruplesCount == 9);
+ Assert.IsTrue(store2.Equals(store1));
+ //Test that automatic datatype discovery happened successfully
+ Assert.IsTrue(RDFDatatypeRegister.GetDatatype($"ex:mydtQQ{(int)format}").TargetDatatype == RDFModelEnums.RDFDatatypes.XSD_STRING);
+ Assert.IsTrue(RDFDatatypeRegister.GetDatatype($"ex:mydtQQ{(int)format}").Facets.Single() is RDFPatternFacet fct && fct.Pattern == "^ex$");
+ }
+
[DataTestMethod]
[DataRow(RDFStoreEnums.RDFFormats.NQuads)]
[DataRow(RDFStoreEnums.RDFFormats.TriX)]
@@ -1262,6 +1338,32 @@ public async Task ShouldImportFromStreamAsync(RDFStoreEnums.RDFFormats format)
Assert.IsTrue(store2.Equals(store1));
}
+ [DataTestMethod]
+ [DataRow(RDFStoreEnums.RDFFormats.NQuads)]
+ [DataRow(RDFStoreEnums.RDFFormats.TriX)]
+ [DataRow(RDFStoreEnums.RDFFormats.TriG)]
+ public async Task ShouldImportFromStreamAsyncWithEnabledDatatypeDiscovery(RDFStoreEnums.RDFFormats format)
+ {
+ MemoryStream stream = new MemoryStream();
+ RDFMemoryStore store1 = new RDFMemoryStore();
+ RDFQuadruple quadruple1 = new RDFQuadruple(new RDFContext("http://ex/ctx/"), new RDFResource("http://ex/subj/"), new RDFResource("http://ex/pred/"), new RDFPlainLiteral("lit", "en-US"));
+ RDFQuadruple quadruple2 = new RDFQuadruple(new RDFContext("http://ex/ctx/"), new RDFResource("http://ex/subj/"), new RDFResource("http://ex/pred/"), new RDFResource("http://ex/obj/"));
+ store1.AddQuadruple(quadruple1)
+ .AddQuadruple(quadruple2)
+ .MergeGraph(new RDFGraph()
+ .AddDatatype(new RDFDatatype(new Uri($"ex:mydtQ{(int)format}"), RDFModelEnums.RDFDatatypes.XSD_STRING, [
+ new RDFPatternFacet("^ex$") ])));
+ store1.ToStream(format, stream);
+ RDFMemoryStore store2 = await RDFMemoryStore.FromStreamAsync(format, new MemoryStream(stream.ToArray()), true);
+
+ Assert.IsNotNull(store2);
+ Assert.IsTrue(store2.QuadruplesCount == 9);
+ Assert.IsTrue(store2.Equals(store1));
+ //Test that automatic datatype discovery happened successfully
+ Assert.IsTrue(RDFDatatypeRegister.GetDatatype($"ex:mydtQ{(int)format}").TargetDatatype == RDFModelEnums.RDFDatatypes.XSD_STRING);
+ Assert.IsTrue(RDFDatatypeRegister.GetDatatype($"ex:mydtQ{(int)format}").Facets.Single() is RDFPatternFacet fct && fct.Pattern == "^ex$");
+ }
+
[DataTestMethod]
[DataRow(RDFStoreEnums.RDFFormats.NQuads)]
[DataRow(RDFStoreEnums.RDFFormats.TriX)]
@@ -1296,6 +1398,28 @@ public void ShouldImportFromDataTable()
Assert.IsTrue(store2.Equals(store1));
}
+ [TestMethod]
+ public void ShouldImportFromDataTableWithEnabledDatatypeDiscovery()
+ {
+ RDFMemoryStore store1 = new RDFMemoryStore();
+ RDFQuadruple quadruple1 = new RDFQuadruple(new RDFContext("http://ctx/"), new RDFResource("http://subj/"), new RDFResource("http://pred/"), new RDFPlainLiteral("lit", "en-US"));
+ RDFQuadruple quadruple2 = new RDFQuadruple(new RDFContext("http://ctx/"), new RDFResource("http://subj/"), new RDFResource("http://pred/"), new RDFResource("http://obj/"));
+ store1.AddQuadruple(quadruple1)
+ .AddQuadruple(quadruple2)
+ .MergeGraph(new RDFGraph()
+ .AddDatatype(new RDFDatatype(new Uri("ex:mydtQ"), RDFModelEnums.RDFDatatypes.XSD_STRING, [
+ new RDFPatternFacet("^ex$") ])));
+ DataTable table = store1.ToDataTable();
+ RDFMemoryStore store2 = RDFMemoryStore.FromDataTable(table, true);
+
+ Assert.IsNotNull(store2);
+ Assert.IsTrue(store2.QuadruplesCount == 9);
+ Assert.IsTrue(store2.Equals(store1));
+ //Test that automatic datatype discovery happened successfully
+ Assert.IsTrue(RDFDatatypeRegister.GetDatatype("ex:mydtQ").TargetDatatype == RDFModelEnums.RDFDatatypes.XSD_STRING);
+ Assert.IsTrue(RDFDatatypeRegister.GetDatatype("ex:mydtQ").Facets.Single() is RDFPatternFacet fct && fct.Pattern == "^ex$");
+ }
+
[TestMethod]
public void ShouldImportEmptyFromDataTable()
{
diff --git a/RDFSharp/Model/RDFGraph.cs b/RDFSharp/Model/RDFGraph.cs
index 0e5c57d5..35016460 100644
--- a/RDFSharp/Model/RDFGraph.cs
+++ b/RDFSharp/Model/RDFGraph.cs
@@ -671,7 +671,8 @@ public static RDFGraph FromFile(RDFModelEnums.RDFFormats rdfFormat, string filep
///
/// Reads a graph from a stream of the given RDF format.
///
- public static RDFGraph FromStream(RDFModelEnums.RDFFormats rdfFormat, Stream inputStream, bool enableDatatypeDiscovery=false) => FromStream(rdfFormat, inputStream, null, enableDatatypeDiscovery);
+ public static RDFGraph FromStream(RDFModelEnums.RDFFormats rdfFormat, Stream inputStream, bool enableDatatypeDiscovery=false)
+ => FromStream(rdfFormat, inputStream, null, enableDatatypeDiscovery);
internal static RDFGraph FromStream(RDFModelEnums.RDFFormats rdfFormat, Stream inputStream, Uri graphContext, bool enableDatatypeDiscovery=false)
{
#region Guards
diff --git a/RDFSharp/Store/Engines/RDFMemoryStore.cs b/RDFSharp/Store/Engines/RDFMemoryStore.cs
index 82721bc2..9183ffa6 100644
--- a/RDFSharp/Store/Engines/RDFMemoryStore.cs
+++ b/RDFSharp/Store/Engines/RDFMemoryStore.cs
@@ -689,7 +689,7 @@ public RDFMemoryStore DifferenceWith(RDFStore store)
///
/// Reads a memory store from a file of the given RDF format.
///
- public static RDFMemoryStore FromFile(RDFStoreEnums.RDFFormats rdfFormat, string filepath)
+ public static RDFMemoryStore FromFile(RDFStoreEnums.RDFFormats rdfFormat, string filepath, bool enableDatatypeDiscovery=false)
{
#region Guards
if (string.IsNullOrWhiteSpace(filepath))
@@ -711,19 +711,29 @@ public static RDFMemoryStore FromFile(RDFStoreEnums.RDFFormats rdfFormat, string
memStore = RDFTriG.Deserialize(filepath);
break;
}
+
+ #region Datatype Discovery
+ if (enableDatatypeDiscovery)
+ {
+ foreach (RDFGraph graph in memStore.ExtractGraphs())
+ foreach (RDFDatatype datatypeDefinition in graph.ExtractDatatypeDefinitions())
+ RDFDatatypeRegister.AddDatatype(datatypeDefinition);
+ }
+ #endregion
+
return memStore;
}
///
/// Asynchronously reads a memory store from a file of the given RDF format.
///
- public static Task FromFileAsync(RDFStoreEnums.RDFFormats rdfFormat, string filepath)
- => Task.Run(() => FromFile(rdfFormat, filepath));
+ public static Task FromFileAsync(RDFStoreEnums.RDFFormats rdfFormat, string filepath, bool enableDatatypeDiscovery=false)
+ => Task.Run(() => FromFile(rdfFormat, filepath, enableDatatypeDiscovery));
///
/// Reads a memory store from a stream of the given RDF format.
///
- public static RDFMemoryStore FromStream(RDFStoreEnums.RDFFormats rdfFormat, Stream inputStream)
+ public static RDFMemoryStore FromStream(RDFStoreEnums.RDFFormats rdfFormat, Stream inputStream, bool enableDatatypeDiscovery=false)
{
#region Guards
if (inputStream == null)
@@ -743,19 +753,29 @@ public static RDFMemoryStore FromStream(RDFStoreEnums.RDFFormats rdfFormat, Stre
memStore = RDFTriG.Deserialize(inputStream);
break;
}
+
+ #region Datatype Discovery
+ if (enableDatatypeDiscovery)
+ {
+ foreach (RDFGraph graph in memStore.ExtractGraphs())
+ foreach (RDFDatatype datatypeDefinition in graph.ExtractDatatypeDefinitions())
+ RDFDatatypeRegister.AddDatatype(datatypeDefinition);
+ }
+ #endregion
+
return memStore;
}
///
/// Asynchronously reads a memory store from a stream of the given RDF format.
///
- public static Task FromStreamAsync(RDFStoreEnums.RDFFormats rdfFormat, Stream inputStream)
- => Task.Run(() => FromStream(rdfFormat, inputStream));
+ public static Task FromStreamAsync(RDFStoreEnums.RDFFormats rdfFormat, Stream inputStream, bool enableDatatypeDiscovery=false)
+ => Task.Run(() => FromStream(rdfFormat, inputStream, enableDatatypeDiscovery));
///
/// Reads a memory store from a datatable with "Context-Subject-Predicate-Object" columns.
///
- public static RDFMemoryStore FromDataTable(DataTable table)
+ public static RDFMemoryStore FromDataTable(DataTable table, bool enableDatatypeDiscovery=false)
{
#region Guards
if (table == null)
@@ -767,6 +787,8 @@ public static RDFMemoryStore FromDataTable(DataTable table)
#endregion
RDFMemoryStore memStore = new RDFMemoryStore();
+
+ #region Parse Table
foreach (DataRow tableRow in table.Rows)
{
#region CONTEXT
@@ -811,19 +833,30 @@ public static RDFMemoryStore FromDataTable(DataTable table)
memStore.AddQuadruple(new RDFQuadruple(new RDFContext(rowCtx.ToString()), (RDFResource)rowSubj, (RDFResource)rowPred, (RDFLiteral)rowObj));
#endregion
}
+ #endregion
+
+ #region Datatype Discovery
+ if (enableDatatypeDiscovery)
+ {
+ foreach (RDFGraph graph in memStore.ExtractGraphs())
+ foreach (RDFDatatype datatypeDefinition in graph.ExtractDatatypeDefinitions())
+ RDFDatatypeRegister.AddDatatype(datatypeDefinition);
+ }
+ #endregion
+
return memStore;
}
///
/// Asynchronously reads a memory store from a datatable with "Context-Subject-Predicate-Object" columns.
///
- public static Task FromDataTableAsync(DataTable table)
- => Task.Run(() => FromDataTable(table));
+ public static Task FromDataTableAsync(DataTable table, bool enableDatatypeDiscovery=false)
+ => Task.Run(() => FromDataTable(table, enableDatatypeDiscovery));
///
/// Reads a memory store by trying to dereference the given Uri
///
- public static RDFMemoryStore FromUri(Uri uri, int timeoutMilliseconds = 20000)
+ public static RDFMemoryStore FromUri(Uri uri, int timeoutMilliseconds=20000, bool enableDatatypeDiscovery=false)
{
#region Guards
if (uri == null)
@@ -880,14 +913,23 @@ public static RDFMemoryStore FromUri(Uri uri, int timeoutMilliseconds = 20000)
throw new RDFStoreException("Cannot read RDF memory store from Uri because: " + ex.Message);
}
+ #region Datatype Discovery
+ if (enableDatatypeDiscovery)
+ {
+ foreach (RDFGraph graph in memStore.ExtractGraphs())
+ foreach (RDFDatatype datatypeDefinition in graph.ExtractDatatypeDefinitions())
+ RDFDatatypeRegister.AddDatatype(datatypeDefinition);
+ }
+ #endregion
+
return memStore;
}
///
/// Asynchronously reads a memory store by trying to dereference the given Uri
///
- public static Task FromUriAsync(Uri uri, int timeoutMilliseconds = 20000)
- => Task.Run(() => FromUri(uri, timeoutMilliseconds));
+ public static Task FromUriAsync(Uri uri, int timeoutMilliseconds=20000, bool enableDatatypeDiscovery=false)
+ => Task.Run(() => FromUri(uri, timeoutMilliseconds, enableDatatypeDiscovery));
#endregion
#endregion