| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| |
| |
| <title>Apache Jena - Presenting RDF as frames</title> |
| <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| |
| <link href="/css/bootstrap.min.css" rel="stylesheet" media="screen"> |
| <link href="/css/bootstrap-icons.css" rel="stylesheet" media="screen"><link rel="stylesheet" type="text/css" href="https://jena.apache.org/sass/jena.1b17c39a117e22b46db4c66f6395dc27c134a60377d87d2d5745b8600eb69722.css" integrity="sha256-GxfDmhF+IrRttMZvY5XcJ8E0pgN32H0tV0W4YA62lyI="> |
| <link rel="shortcut icon" href="/images/favicon.ico" /> |
| |
| </head> |
| |
| <body> |
| |
| <nav class="navbar navbar-expand-lg bg-body-tertiary" role="navigation"> |
| <div class="container"> |
| <div class="navbar-header"> |
| <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> |
| <span class="navbar-toggler-icon"></span> |
| </button> |
| <a class="navbar-brand" href="/index.html"> |
| <img class="logo-menu" src="/images/jena-logo/jena-logo-notext-small.png" alt="jena logo">Apache Jena</a> |
| </div> |
| |
| <div class="collapse navbar-collapse" id="navbarNav"> |
| <ul class="navbar-nav me-auto mb-2 mb-lg-0"> |
| <li id="homepage" class="nav-item"><a class="nav-link" href="/index.html"><span class="bi-house"></span> Home</a></li> |
| <li id="download" class="nav-item"><a class="nav-link" href="/download/index.cgi"><span class="bi-download"></span> Download</a></li> |
| <li class="nav-item dropdown"> |
| <a href="#" class="nav-link dropdown-toggle" role="button" data-bs-toggle="dropdown" aria-expanded="false"><span class="bi-journal"></span> Learn <b class="caret"></b></a> |
| <ul class="dropdown-menu"> |
| <li class="dropdown-header">Tutorials</li> |
| <li><a class="dropdown-item" href="/tutorials/index.html">Overview</a></li> |
| <li><a class="dropdown-item" href="/documentation/fuseki2/index.html">Fuseki Triplestore</a></li> |
| <li><a class="dropdown-item" href="/documentation/notes/index.html">How-To's</a></li> |
| <li><a class="dropdown-item" href="/documentation/query/manipulating_sparql_using_arq.html">Manipulating SPARQL using ARQ</a></li> |
| <li><a class="dropdown-item" href="/tutorials/rdf_api.html">RDF core API tutorial</a></li> |
| <li><a class="dropdown-item" href="/tutorials/sparql.html">SPARQL tutorial</a></li> |
| <li><a class="dropdown-item" href="/tutorials/using_jena_with_eclipse.html">Using Jena with Eclipse</a></li> |
| <li class="dropdown-divider"></li> |
| <li class="dropdown-header">References</li> |
| <li><a class="dropdown-item" href="/documentation/index.html">Overview</a></li> |
| <li><a class="dropdown-item" href="/documentation/query/index.html">ARQ (SPARQL)</a></li> |
| <li><a class="dropdown-item" href="/documentation/io/">RDF I/O</a></li> |
| <li><a class="dropdown-item" href="/documentation/assembler/index.html">Assembler</a></li> |
| <li><a class="dropdown-item" href="/documentation/tools/index.html">Command-line tools</a></li> |
| <li><a class="dropdown-item" href="/documentation/rdfs/">Data with RDFS Inferencing</a></li> |
| <li><a class="dropdown-item" href="/documentation/geosparql/index.html">GeoSPARQL</a></li> |
| <li><a class="dropdown-item" href="/documentation/inference/index.html">Inference API</a></li> |
| <li><a class="dropdown-item" href="/documentation/ontology/">Ontology API</a></li> |
| <li><a class="dropdown-item" href="/documentation/permissions/index.html">Permissions</a></li> |
| <li><a class="dropdown-item" href="/documentation/extras/querybuilder/index.html">Query Builder</a></li> |
| <li><a class="dropdown-item" href="/documentation/rdf/index.html">RDF API</a></li> |
| <li><a class="dropdown-item" href="/documentation/rdfconnection/">RDF Connection - SPARQL API</a></li> |
| <li><a class="dropdown-item" href="/documentation/rdfstar/index.html">RDF-star</a></li> |
| <li><a class="dropdown-item" href="/documentation/shacl/index.html">SHACL</a></li> |
| <li><a class="dropdown-item" href="/documentation/shex/index.html">ShEx</a></li> |
| <li><a class="dropdown-item" href="/documentation/tdb/index.html">TDB</a></li> |
| <li><a class="dropdown-item" href="/documentation/tdb2/index.html">TDB2</a></li> |
| <li><a class="dropdown-item" href="/documentation/query/text-query.html">Text Search</a></li> |
| </ul> |
| </li> |
| |
| <li class="nav-item dropdown"> |
| <a href="#" class="nav-link dropdown-toggle" role="button" data-bs-toggle="dropdown" aria-expanded="false"><span class="bi-journal-code"></span> Javadoc <b class="caret"></b></a> |
| <ul class="dropdown-menu"> |
| <li><a class="dropdown-item" href="/documentation/javadoc.html">All Javadoc</a></li> |
| <li><a class="dropdown-item" href="/documentation/javadoc/arq/">ARQ</a></li> |
| <li><a class="dropdown-item" href="/documentation/javadoc/fuseki2/">Fuseki</a></li> |
| <li><a class="dropdown-item" href="/documentation/javadoc/geosparql/">GeoSPARQL</a></li> |
| <li><a class="dropdown-item" href="/documentation/javadoc/jena/">Jena Core</a></li> |
| <li><a class="dropdown-item" href="/documentation/javadoc/permissions/">Permissions</a></li> |
| <li><a class="dropdown-item" href="/documentation/javadoc/extras/querybuilder/">Query Builder</a></li> |
| <li><a class="dropdown-item" href="/documentation/javadoc/shacl/">SHACL</a></li> |
| <li><a class="dropdown-item" href="/documentation/javadoc/tdb/">TDB</a></li> |
| <li><a class="dropdown-item" href="/documentation/javadoc/text/">Text Search</a></li> |
| </ul> |
| </li> |
| </ul> |
| <form class="d-flex" role="search" action="/search" method="GET"> |
| <div class="input-group"> |
| <input class="form-control border-end-0 border m-0" type="search" name="q" id="search-query" placeholder="Search...." aria-label="Search" style="width: 10rem;"> |
| <button class="btn btn-outline-secondary border-start-0 border" type="submit"> |
| <i class="bi-search"></i> |
| </button> |
| </div> |
| </form> |
| <ul class="navbar-nav"> |
| <li id="ask" class="nav-item"><a class="nav-link" href="/help_and_support/index.html" title="Ask"><span class="bi-patch-question"></span><span class="text-body d-none d-xxl-inline"> Ask</span></a></li> |
| |
| <li class="nav-item dropdown"> |
| <a href="#" title="Get involved" class="nav-link dropdown-toggle" role="button" data-bs-toggle="dropdown" aria-expanded="false"><span class="bi-megaphone"></span><span class="text-body d-none d-xxl-inline"> Get involved </span><b class="caret"></b></a> |
| <ul class="dropdown-menu"> |
| <li><a class="dropdown-item" href="/getting_involved/index.html">Contribute</a></li> |
| <li><a class="dropdown-item" href="/help_and_support/bugs_and_suggestions.html">Report a bug</a></li> |
| <li class="dropdown-divider"></li> |
| <li class="dropdown-header">Project</li> |
| <li><a class="dropdown-item" href="/about_jena/about.html">About Jena</a></li> |
| <li><a class="dropdown-item" href="/about_jena/architecture.html">Architecture</a></li> |
| <li><a class="dropdown-item" href="/about_jena/citing.html">Citing</a></li> |
| <li><a class="dropdown-item" href="/about_jena/team.html">Project team</a></li> |
| <li><a class="dropdown-item" href="/about_jena/contributions.html">Related projects</a></li> |
| <li><a class="dropdown-item" href="/about_jena/roadmap.html">Roadmap</a></li> |
| <li><a class="dropdown-item" href="/about_jena/security-advisories.html">Security Advisories</a></li> |
| <li class="dropdown-divider"></li> |
| <li class="dropdown-header">ASF</li> |
| <li><a class="dropdown-item" href="https://www.apache.org/">Apache Software Foundation</a></li> |
| <li><a class="dropdown-item" href="https://www.apache.org/foundation/sponsorship.html">Become a Sponsor</a></li> |
| <li><a class="dropdown-item" href="https://www.apache.org/licenses/LICENSE-2.0">License</a></li> |
| <li><a class="dropdown-item" href="https://www.apache.org/security/">Security</a></li> |
| <li><a class="dropdown-item" href="https://www.apache.org/foundation/thanks.html">Thanks</a></li> |
| </ul> |
| </li> |
| |
| |
| |
| |
| <li class="nav-item" id="edit"><a class="nav-link" href="https://github.com/apache/jena-site/edit/main/source/documentation/notes/rdf-frames.md" title="Edit this page on GitHub"><span class="bi-pencil-square"></span><span class="text-body d-none d-xxl-inline"> Edit this page</span></a></li> |
| </ul> |
| </div> |
| </div> |
| </nav> |
| |
| <div class="container"> |
| <div class="row"> |
| <div class="col-md-12"> |
| |
| <div id="breadcrumbs"> |
|
|
|
|
|
|
|
|
|
|
|
|
| <ol class="breadcrumb mt-4 p-2 bg-body-tertiary">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| <li class="breadcrumb-item"><a href='/documentation'>DOCUMENTATION</a></li>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| <li class="breadcrumb-item"><a href='/documentation/notes'>NOTES</a></li>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| <li class="breadcrumb-item active">RDF FRAMES</li>
|
|
|
|
|
|
|
|
|
| </ol>
|
|
|
|
|
|
|
| |
| </div> |
| <h1 class="title">Presenting RDF as frames</h1> |
| |
| |
| <main class="d-flex flex-xl-row flex-column"> |
| |
| <aside class="text-muted align-self-start mb-3 p-0 d-xl-none d-block"> |
| <h2 class="h6 sticky-top m-0 p-2 bg-body-tertiary">On this page</h2> |
| <nav id="TableOfContents"> |
| <ul> |
| <li><a href="#basic-principles-the-properties-of-a-class">Basic principles: the properties of a class</a></li> |
| <li><a href="#sub-classes-and-more-complex-class-expressions">Sub-classes, and more complex class expressions</a></li> |
| <li><a href="#property-hierarchies">Property hierarchies</a></li> |
| <li><a href="#global-properties">Global properties</a></li> |
| <li><a href="#the-java-api">The Java API</a></li> |
| </ul> |
| </nav> |
| </aside> |
| <article class="flex-column me-lg-4"> |
| <p>The origins of RDF as a representation language include |
| <em>frame languages</em>, in which an object, or frame, was the main unit |
| of structuring data. Frames have <em>slots</em>, for example a <code>Person</code> |
| frame might have an <code>age</code> slot, a <code>height</code>slot etc. RDF, however, |
| has taken a step beyond frame languages by making <code>rdf:Property</code> a |
| first class value, not an element of a frame or resource <em>per se</em>. |
| In RDF, for example, an age property can be defined: |
| <code><rdf:Property rdf:ID="age"></code>, and then applied to any resource, |
| including, but not limited to a <code>Person</code> resource.</p> |
| <p>While this introduces an extra element of modelling flexibility in |
| RDF, it is often the case that users want to treat some components |
| in their models in a more structured way, similar to the original |
| idea of frames. It is often assumed that <code>rdfs:domain</code> restricts a |
| property to be used only on resources that are in the domain class. |
| For example, a frequently asked question on the Jena support list |
| is why the following is not an error:</p> |
| <pre><code> <rdfs:Class rdf:ID="Person" /> |
| <rdfs:Class rdf:ID="Truck" /> |
| <rdf:Property rdf:ID="age"> |
| <rdfs:domain rdf:resource="Person" /> |
| </rdf:Property> |
| |
| <Truck rdf:ID="truck1"> |
| <age>2</age> |
| </Truck> |
| </code></pre> |
| <p>Whereas many object-oriented or frame-oriented representations |
| would regard it as an error that the <code>age</code> property was not being |
| applied to a <code>Person</code>, RDF-based applications are simply entitled |
| to infer that <code>truck1</code> is a (that is, has <code>rdf:type</code>) <code>Truck</code> as |
| well as a <code>Person</code>. This is unlikely to be the case in any |
| real-world domain, but it is a valid RDF inference.</p> |
| <p>A consequence of RDF’s design is that it is not really possible to |
| answer the commonly asked question “Which properties can be applied |
| to resources of class <em>C</em>?”. Strictly speaking, the RDF answer is |
| “Any property”. However, many developers have a legitimate |
| requirement to present a composite view of classes and their |
| associated properties, forming more a more succinct structuring of |
| an ontology or schema. The purpose of this note is to explain the |
| mechanisms built-in to Jena to support a frame-like view of |
| resources, while remaining correct with respect to RDF (and OWL) |
| semantics.</p> |
| <h2 id="basic-principles-the-properties-of-a-class">Basic principles: the properties of a class</h2> |
| <p>Since any RDF property can be applied to any RDF resource, we |
| require a definition of the properties of a given class that |
| respects RDF semantics. Consider the following RDF fragment:</p> |
| <pre><code> <rdfs:Class rdf:ID="Person" /> |
| <rdf:Property rdf:ID="age" /> |
| |
| <Person rdf:ID="jane_doe"> |
| <age>23</a> |
| </Person> |
| </code></pre> |
| <p>Now consider that we add to this fragment that:</p> |
| <pre><code> <rdf:Property rdf:about="age"> |
| <rdfs:domain rdf:resource="Person" /> |
| </rdf:Property> |
| </code></pre> |
| <p>This additional information about the domain of the <code>age</code> property |
| does not add any new entailments to the model. Why? Because we |
| already know that <code>jane_doe</code> is a Person. So we can consider <code>age</code> |
| to be one of the properties of <code>Person</code> type resources, because if |
| we use the property as a predicate of that resource, it doesn’t add |
| any new <code>rdf:type</code> information about the resource. Conversely, if |
| we know that some resource has an <code>age</code>, we don’t learn any new |
| information by declaring that it has <code>rdf:type Person</code>. In summary, |
| for the purposes of this HOWTO we define the |
| <em>properties of a class</em> as just those properties that don’t entail |
| any new type information when applied to resources that are already |
| known to be of that class.</p> |
| <h2 id="sub-classes-and-more-complex-class-expressions">Sub-classes, and more complex class expressions</h2> |
| <p>Given these basic principles, now consider the following RDF |
| fragment:</p> |
| <pre><code> <rdfs:Class rdf:ID="LivingThing" /> |
| |
| <rdfs:Class rdf:ID="Animal"> |
| <rdfs:subClassOf rdf:resource="#LivingThing"> |
| </rdfs:Class> |
| |
| <rdfs:Class rdf:ID="Mammal"> |
| <rdfs:subClassOf rdf:resource="#Animal"> |
| </rdfs:Class> |
| |
| <rdf:Property rdf:ID="hasSkeleton"> |
| <rdfs:domain rdf:resource="Animal" /> |
| </rdf:Property> |
| </code></pre> |
| <p>Is <code>hasSkeleton</code> one of the properties of <code>Animal</code>? Yes, because |
| any resource of <code>rdf:type Animal</code> can have a <code>hasSkeleton</code> property |
| (with value either true or false) without adding type information. |
| Similarly, any resource that is a <code>Mammal</code> also has |
| <code>rdf:type Animal</code> (by the sub-class relation), so <code>hasSkeleton</code> is |
| a property of <code>Mammal</code>. However, <code>hasSkeleton</code> is <em>not</em> a property |
| of <code>LivingThing</code>, since we don’t automatically know that a living |
| thing is an animal - it may be a plant. Stating that a given |
| <code>LivingThing</code> has a <code>hasSkeleton</code> property, even if the value is |
| false, would entail the additional <code>rdf:type</code> statement that the |
| <code>LivingThing</code> is also an <code>Animal</code>.</p> |
| <p>For more complex class expressions in the domain, we look to see |
| what simple domain constraints are entailed. For example, a domain |
| constraint <code>A ∩ B</code> (i.e. “A intersection B”) for property <code>p</code> |
| entails that both <code>p rdfs:domain A</code> and <code>p rdfs:domain B</code> are true. |
| However, the properties of neither <code>A</code> nor <code>B</code> will include <code>p</code>. To |
| see this, suppose we have a resource <code>x</code> that we already know is of |
| type <code>A</code>, and a statement <code>x p y</code>. This entails <code>x rdf:type A</code> |
| which we already know, but also <code>x rdf:type B</code>. So information is |
| added, even if we know that <code>x</code> is an instance <code>A</code>, so <code>p</code> is not a |
| property of <code>A</code>. The symmetrical argument holds for <code>p</code> not being a |
| property of <code>B</code>.</p> |
| <p>However, if the domain of <code>p</code> is <code>A ∪ B</code> (i.e. “A union B”), then |
| both <code>A</code> and <code>B</code> will have <code>p</code> as a property, since an occurrence |
| of, say <code>x p y</code> does not allow us to conclude that either |
| <code>x rdf:type A</code> or <code>x rdf:type B</code>.</p> |
| <h2 id="property-hierarchies">Property hierarchies</h2> |
| <p>Since sub-properties inherit the domain constraints of their parent |
| property, the properties of a class will include the closure over |
| the sub-property hierarchy. Extending the previous example, the |
| properties of <code>Animal</code> and <code>Mammal</code> include both <code>hasSkeleton</code> and |
| <code>hasEndoSkeleton</code>:</p> |
| <pre><code> <rdf:Property rdf:ID="hasSkeleton"> |
| <rdfs:domain rdf:resource="Animal" /> |
| </rdf:Property> |
| |
| <rdf:Property rdf:ID="hasEndoSkeleton"> |
| <rdfs:subPropertyOf rdf:resource="#hasSkeleton" /> |
| </rdf:Property> |
| </code></pre> |
| <p>In general, there may be many different ways of deducing simple |
| domain constraints from the axioms asserted in the ontology. |
| Whether or not all of these possible deductions are present in any |
| given RDF model depends on the power and completeness of the |
| reasoner bound to that model.</p> |
| <h2 id="global-properties">Global properties</h2> |
| <p>Under the principled definition that we propose here, properties |
| which do not express a domain value are <em>global</em>, in the sense that |
| they can apply to any resource. They do not, by definition, entail |
| any new type information about the individuals they are applied to. |
| Put another way, the domain of a property, if unspecified, is |
| either <code>rdfs:Resource</code> or <code>owl:Thing</code>, depending on the ontology |
| language. These are simply the types that all resources have by |
| default. Therefore, every class has all of the global properties as |
| one of the properties of the class.</p> |
| <p>A commonly used idiom in some OWL ontologies is to use |
| <em>Restrictions</em> to create an association between a class and the |
| properties of instances of that class. For example, the following |
| fragment shows that all instances of <code>Person</code> should have a |
| <code>familyName</code> property:</p> |
| <pre><code> <owl:Class rdf:ID="Person"> |
| <rdfs:subClassOf> |
| <owl:Restriction> |
| <owl:onProperty rdf:resource="#familyName" /> |
| <owl:minCardinality rdf:datatype="&xsd;int">1</owl:minCardinality> |
| </owl:Restriction> |
| </rdfs:subClassOf> |
| </owl:Class> |
| </code></pre> |
| <p>This approach shows the intent of the ontology designer that |
| <code>Person</code> instances have <code>familyName</code> properties. We do regard |
| <code>familyName</code> as one of the <em>properties of</em> <code>Person</code>, but only |
| because of the global properties principle. Unless a domain |
| constraint is also specified for <code>familyName</code>, it will appear as |
| one of the properties of classes other than <code>Person</code>. |
| <strong>Note that this is a behaviour change from versions of Jena prior to release 2.2</strong>. |
| Prior to this release, Jena used a heuristic method to attempt to |
| associate restriction properties with the classes sub-classing that |
| restriction. Since there were problems with precisely defining the |
| heuristic, and ensuring correct behaviour (especially with |
| inference models), we have dropped the use of this heuristic from |
| Jena 2.2 onwards.</p> |
| <h2 id="the-java-api">The Java API</h2> |
| <p>Support for frame-like views of classes and properties is provided |
| through the <a href="../ontology/index.html">ontology API</a>. The following |
| methods are used to access the properties of a class, and the |
| converse for properties:</p> |
| <pre><code> OntClass.declaredProperties(); |
| OntClass.declaredProperties( boolean direct ); |
| OntClass.hasDeclaredProperty( OntProperty prop, boolean direct ); |
| OntProperty.declaringClasses(); |
| OntProperty.declaringClasses( boolean direct ); |
| </code></pre> |
| <p>All of the above API methods return a Java <code>Stream</code>.</p> |
| <p>Where |
| <code>direct</code> is used to restrict the associations to only the local |
| associations. |
| See also |
| <a href="../ontology/index.html##instances-or-individuals">Figure 5: asserted and inferred relationships</a>.</p> |
| <p>The models that are constructed without reasoners perform only very limited simulation |
| of the inference closure of the model. Users who wish the declared |
| properties to include entailments will need to construct their |
| models with one of the built-in or external reasoners. The |
| difference is illustrated by the following code fragment:</p> |
| <pre><code>String ns = "http://ex.com#"; |
| OntModel m0 = OntModelFactory.createModel(OntSpecification.OWL2_DL_MEM); |
| OntClass a0 = m0.createOntClass( ns + "A" ); |
| OntClass b0 = m0.createOntClass( ns + "B" ); |
| OntObjectProperty p = m0.createObjectProperty(ns + "p"); |
| OntObjectProperty q = m0.createObjectProperty(ns + "q"); |
| p.addSuperProperty(q); |
| q.addDomain(b0); |
| a0.addSubClass(b0); |
| |
| OntModel m1 = OntModelFactory.createModel(m0.getBaseGraph(), OntSpecification.OWL2_DL_MEM_RULES_INF); |
| OntClass b1 = m1.getOntClass( ns + "B" ); |
| |
| Stream<OntProperty> s0 = b0.declaredProperties(true); |
| Stream<OntProperty> s1 = b0.declaredProperties(true); |
| </code></pre> |
| <p>Stream <code>s1</code> will return <code>p</code> and <code>q</code>, while <code>s0</code> will return only |
| <code>q</code>.</p> |
| |
| </article> |
| |
| <aside class="text-muted align-self-start mb-3 mb-xl-5 p-0 d-none d-xl-flex flex-column sticky-top"> |
| <h2 class="h6 sticky-top m-0 p-2 bg-body-tertiary">On this page</h2> |
| <nav id="TableOfContents"> |
| <ul> |
| <li><a href="#basic-principles-the-properties-of-a-class">Basic principles: the properties of a class</a></li> |
| <li><a href="#sub-classes-and-more-complex-class-expressions">Sub-classes, and more complex class expressions</a></li> |
| <li><a href="#property-hierarchies">Property hierarchies</a></li> |
| <li><a href="#global-properties">Global properties</a></li> |
| <li><a href="#the-java-api">The Java API</a></li> |
| </ul> |
| </nav> |
| </aside> |
| </main> |
| |
| </div> |
| </div> |
| </div> |
| |
| <footer class="bd-footer py-4 py-md-5 mt-4 mt-lg-5 bg-body-tertiary"> |
| <div class="container" style="font-size:80%" > |
| <p> |
| Copyright © 2011–2024 The Apache Software Foundation, Licensed under the |
| <a href="https://www.apache.org/licenses/LICENSE-2.0">Apache License, Version 2.0</a>. |
| </p> |
| <p> |
| Apache Jena, Jena, the Apache Jena project logo, Apache and the Apache feather logos are trademarks of |
| The Apache Software Foundation. |
| <br/> |
| <a href="https://privacy.apache.org/policies/privacy-policy-public.html" |
| >Apache Software Foundation Privacy Policy</a>. |
| </p> |
| </div> |
| </footer> |
| |
| <script src="/js/popper.min.js.js" type="text/javascript"></script> |
| <script src="/js/bootstrap.min.js" type="text/javascript"></script> |
| <script src="/js/improve.js" type="text/javascript"></script> |
| |
| <script type="text/javascript"> |
| (function() { |
| 'use strict' |
| |
| |
| |
| const links = document.querySelectorAll(`a[href="${window.location.pathname}"]`) |
| if (links !== undefined && links !== null) { |
| for (const link of links) { |
| |
| link.classList.add('active') |
| let parentElement = link.parentElement |
| let count = 0 |
| const levelsLimit = 4 |
| |
| |
| |
| |
| |
| while (['UL', 'LI'].includes(parentElement.tagName) && count <= levelsLimit) { |
| if (parentElement.tagName === 'LI') { |
| |
| |
| |
| parentElement.querySelector('a:first-child').classList.add('active') |
| } |
| parentElement = parentElement.parentElement |
| count++ |
| } |
| } |
| } |
| })() |
| </script> |
| |
| </body> |
| </html> |