Completed
Push — master ( 5f1b92...b77c46 )
by mw
17s
created

SMWRDFXMLSerializer   B

Complexity

Total Complexity 37

Size/Duplication

Total Lines 260
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 94.07%

Importance

Changes 1
Bugs 0 Features 1
Metric Value
wmc 37
c 1
b 0
f 1
lcom 1
cbo 3
dl 0
loc 260
ccs 111
cts 118
cp 0.9407
rs 8.6

13 Methods

Rating   Name   Duplication   Size   Complexity  
A clear() 0 5 1
B serializeHeader() 0 28 1
A serializeFooter() 0 4 1
A serializeDeclaration() 0 3 1
A serializeExpData() 0 9 2
A flushContent() 0 6 1
C serializeNestedExpData() 0 58 13
A serializeNamespace() 0 9 2
A serializeExpLiteral() 0 17 4
A serializeExpCollection() 0 16 4
B serializeExpResource() 0 16 5
A makeValueEntityString() 0 3 1
A makeAttributeValueString() 0 3 1
1
<?php
2
3
/**
4
 * File holding the SMWRDFXMLSerializer class that provides basic functions for
5
 * serialising OWL data in RDF/XML syntax.
6
 *
7
 * @ingroup SMW
8
 *
9
 * @author Markus Krötzsch
10
 */
11
12
/**
13
 * Class for serializing exported data (encoded as SMWExpData object) in
14
 * RDF/XML.
15
 *
16
 * @ingroup SMW
17
 */
18
class SMWRDFXMLSerializer extends SMWSerializer{
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
19
	/**
20
	 * True if the $pre_ns_buffer contains the beginning of a namespace
21
	 * declaration block to which further declarations for the current
22
	 * context can be appended.
23
	 */
24
	protected $namespace_block_started;
25
	/**
26
	 * True if the namespaces that are added at the current serialization stage
27
	 * become global, i.e. remain available for all later contexts. This is the
28
	 * case in RDF/XML only as long as the header has not been streamed to the
29
	 * client (reflected herein by calling flushContent()). Later, namespaces
30
	 * can only be added locally to individual elements, thus requiring them to
31
	 * be re-added multiple times if used in many elements.
32
	 */
33
	protected $namespaces_are_global;
34
35 14
	public function clear() {
36 14
		parent::clear();
37 14
		$this->namespaces_are_global = false;
38 14
		$this->namespace_block_started = false;
39 14
	}
40
41 14
	protected function serializeHeader() {
42 14
		$this->namespaces_are_global = true;
43 14
		$this->namespace_block_started = true;
44 14
		$this->pre_ns_buffer =
45
			"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" .
46
			"<!DOCTYPE rdf:RDF[\n" .
47 14
			"\t<!ENTITY rdf " . $this->makeValueEntityString( SMWExporter::getInstance()->expandURI( '&rdf;' ) ) . ">\n" .
48 14
			"\t<!ENTITY rdfs " . $this->makeValueEntityString( SMWExporter::getInstance()->expandURI( '&rdfs;' ) ) . ">\n" .
49 14
			"\t<!ENTITY owl " . $this->makeValueEntityString( SMWExporter::getInstance()->expandURI( '&owl;' ) ) . ">\n" .
50 14
			"\t<!ENTITY swivt " . $this->makeValueEntityString( SMWExporter::getInstance()->expandURI( '&swivt;' ) ) . ">\n" .
51
			// A note on "wiki": this namespace is crucial as a fallback when it would be illegal to start e.g. with a number.
52
			// In this case, one can always use wiki:... followed by "_" and possibly some namespace, since _ is legal as a first character.
53 14
			"\t<!ENTITY wiki "  . $this->makeValueEntityString( SMWExporter::getInstance()->expandURI( '&wiki;' ) ) . ">\n" .
54 14
			"\t<!ENTITY category " . $this->makeValueEntityString( SMWExporter::getInstance()->expandURI( '&category;' ) ) . ">\n" .
55 14
			"\t<!ENTITY property " . $this->makeValueEntityString( SMWExporter::getInstance()->expandURI( '&property;' ) ) . ">\n" .
56 14
			"\t<!ENTITY wikiurl " . $this->makeValueEntityString( SMWExporter::getInstance()->expandURI( '&wikiurl;' ) ) . ">\n" .
57 14
			"]>\n\n" .
58 14
			"<rdf:RDF\n" .
59 14
			"\txmlns:rdf=\"&rdf;\"\n" .
60 14
			"\txmlns:rdfs=\"&rdfs;\"\n" .
61 14
			"\txmlns:owl =\"&owl;\"\n" .
62 14
			"\txmlns:swivt=\"&swivt;\"\n" .
63 14
			"\txmlns:wiki=\"&wiki;\"\n" .
64 14
			"\txmlns:category=\"&category;\"\n" .
65 14
			"\txmlns:property=\"&property;\"";
66 14
		$this->global_namespaces = array( 'rdf' => true, 'rdfs' => true, 'owl' => true, 'swivt' => true, 'wiki' => true, 'property' => true, 'category' => true );
67 14
		$this->post_ns_buffer .= ">\n\n";
68 14
	}
69
70 14
	protected function serializeFooter() {
71 14
		$this->post_ns_buffer .= "\t<!-- Created by Semantic MediaWiki, https://www.semantic-mediawiki.org/ -->\n";
72 14
		$this->post_ns_buffer .= '</rdf:RDF>';
73 14
	}
74
75 14
	public function serializeDeclaration( $uri, $typename ) {
76 14
		$this->post_ns_buffer .= "\t<$typename rdf:about=\"$uri\" />\n";
77 14
	}
78
79 14
	public function serializeExpData( SMWExpData $expData ) {
80 14
		$this->serializeNestedExpData( $expData, '' );
81 14
		$this->serializeNamespaces();
82 14
		if ( !$this->namespaces_are_global ) {
83 1
			$this->pre_ns_buffer .= $this->post_ns_buffer;
84 1
			$this->post_ns_buffer = '';
85 1
			$this->namespace_block_started = false;
86
		}
87 14
	}
88
89 14
	public function flushContent() {
90 14
		$result = parent::flushContent();
91 14
		$this->namespaces_are_global = false; // must not be done before calling the parent method (which may declare namespaces)
92 14
		$this->namespace_block_started = false;
93 14
		return $result;
94
	}
95
96 2
	protected function serializeNamespace( $shortname, $uri ) {
97 2
		if ( $this->namespaces_are_global ) {
98 2
			$this->global_namespaces[$shortname] = true;
99 2
			$this->pre_ns_buffer .= "\n\t";
100
		} else {
101
			$this->pre_ns_buffer .= ' ';
102
		}
103 2
		$this->pre_ns_buffer .= "xmlns:$shortname=\"$uri\"";
104 2
	}
105
106
	/**
107
	 * Serialize the given SMWExpData object, possibly recursively with
108
	 * increased indentation.
109
	 *
110
	 * @param $expData SMWExpData containing the data to be serialised.
111
	 * @param $indent string specifying a prefix for indentation (usually a sequence of tabs)
112
	 */
113 14
	protected function serializeNestedExpData( SMWExpData $expData, $indent ) {
114 14
		$this->recordDeclarationTypes( $expData );
115
116 14
		$type = $expData->extractMainType()->getQName();
117 14
		if ( !$this->namespace_block_started ) { // start new ns block
118 1
			$this->pre_ns_buffer .= "\t$indent<$type";
119 1
			$this->namespace_block_started = true;
120
		} else { // continue running block
121 14
			$this->post_ns_buffer .= "\t$indent<$type";
122
		}
123
124 14
		if ( ( $expData->getSubject() instanceof SMWExpResource ) &&
0 ignored issues
show
Bug introduced by
The class SMWExpResource does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
125 14
		      !$expData->getSubject()->isBlankNode() ) {
126 14
			 $this->post_ns_buffer .= ' rdf:about="' . $expData->getSubject()->getUri() . '"';
127
		} // else: blank node, no "rdf:about"
128
129 14
		if ( count( $expData->getProperties() ) == 0 ) { // nothing else to export
130
			$this->post_ns_buffer .= " />\n";
131
		} else { // process data
132 14
			$this->post_ns_buffer .= ">\n";
133
134 14
			foreach ( $expData->getProperties() as $property ) {
135 14
				$prop_decl_queued = false;
136 14
				$isClassTypeProp = $this->isOWLClassTypeProperty( $property );
137
138 14
				foreach ( $expData->getValues( $property ) as $valueElement ) {
139 14
					$this->requireNamespace( $property->getNamespaceID(), $property->getNamespace() );
140
141 14
					if ( $valueElement instanceof SMWExpLiteral ) {
0 ignored issues
show
Bug introduced by
The class SMWExpLiteral does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
142 14
						$prop_decl_type = SMW_SERIALIZER_DECL_APROP;
143 14
						$this->serializeExpLiteral( $property, $valueElement, "\t\t$indent" );
144
					} elseif ( $valueElement instanceof SMWExpResource ) {
0 ignored issues
show
Bug introduced by
The class SMWExpResource does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
145 14
						$prop_decl_type = SMW_SERIALIZER_DECL_OPROP;
146 14
						$this->serializeExpResource( $property, $valueElement, "\t\t$indent", $isClassTypeProp );
147
					} elseif ( $valueElement instanceof SMWExpData ) {
148 1
						$prop_decl_type = SMW_SERIALIZER_DECL_OPROP;
149
150 1
						$collection = $valueElement->getCollection();
151 1
						if ( $collection !== false ) { // RDF-style collection (list)
152
							$this->serializeExpCollection( $property, $collection, "\t\t$indent", $isClassTypeProp );
153 1
						} elseif ( count( $valueElement->getProperties() ) > 0 ) { // resource with data
154
							$this->post_ns_buffer .= "\t\t$indent<" . $property->getQName() . ">\n";
155
							$this->serializeNestedExpData( $valueElement, "\t\t$indent" );
156
							$this->post_ns_buffer .= "\t\t$indent</" . $property->getQName() . ">\n";
157
						} else { // resource without data
158 1
							$this->serializeExpResource( $property, $valueElement->getSubject(), "\t\t$indent", $isClassTypeProp );
159
						}
160
					} // else: no other types of export elements
161
162 14
					if ( !$prop_decl_queued ) {
163 14
						$this->requireDeclaration( $property, $prop_decl_type );
0 ignored issues
show
Bug introduced by
The variable $prop_decl_type does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
164 14
						$prop_decl_queued = true;
165
					}
166
				}
167
			}
168 14
			$this->post_ns_buffer .= "\t$indent</" . $type . ">\n";
169
		}
170 14
	}
171
172
	/**
173
	 * Add to the output a serialization of a property assignment where an
174
	 * SMWExpLiteral is the object. It is assumed that a suitable subject
175
	 * block has already been openend.
176
	 *
177
	 * @param $expResourceProperty SMWExpNsResource the property to use
178
	 * @param $expLiteral SMWExpLiteral the data value to use
179
	 * @param $indent string specifying a prefix for indentation (usually a sequence of tabs)
180
	 */
181 14
	protected function serializeExpLiteral( SMWExpNsResource $expResourceProperty, SMWExpLiteral $expLiteral, $indent ) {
182 14
		$this->post_ns_buffer .= $indent . '<' . $expResourceProperty->getQName();
183
184
		// https://www.w3.org/TR/rdf-syntax-grammar/#section-Syntax-languages
185
		// "... to indicate that the included content is in the given language.
186
		// Typed literals which includes XML literals are not affected by this
187
		// attribute. The most specific in-scope language present (if any) is
188
		// applied to property element string literal ..."
189 14
		if ( $expLiteral->getDatatype() !== '' && $expLiteral->getLang() !== '' ) {
190 1
			$this->post_ns_buffer .= ' xml:lang="' . $expLiteral->getLang() . '"';
191 14
		} elseif ( $expLiteral->getDatatype() !== '' ) {
192 14
			$this->post_ns_buffer .= ' rdf:datatype="' . $expLiteral->getDatatype() . '"';
193
		}
194
195 14
		$this->post_ns_buffer .= '>' . $this->makeAttributeValueString( $expLiteral->getLexicalForm() );
196 14
		$this->post_ns_buffer .= '</' . $expResourceProperty->getQName() . ">\n";
197 14
	}
198
199
	/**
200
	 * Add to the output a serialization of a property assignment where an
201
	 * SMWExpResource is the object. It is assumed that a suitable subject
202
	 * block has already been openend.
203
	 *
204
	 * @param $expResourceProperty SMWExpNsResource the property to use
205
	 * @param $expResource SMWExpResource the data value to use
206
	 * @param $indent string specifying a prefix for indentation (usually a sequence of tabs)
207
	 * @param $isClassTypeProp boolean whether the resource must be declared as a class
208
	 */
209 14
	protected function serializeExpResource( SMWExpNsResource $expResourceProperty, SMWExpResource $expResource, $indent, $isClassTypeProp ) {
210 14
		$this->post_ns_buffer .= $indent . '<' . $expResourceProperty->getQName();
211 14
		if ( !$expResource->isBlankNode() ) {
212 14
			if ( ( $expResource instanceof SMWExpNsResource ) && ( $expResource->getNamespaceID() == 'wiki' ) ) {
0 ignored issues
show
Bug introduced by
The class SMWExpNsResource does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
213
				// very common case, reduce bandwidth
214 9
				$this->post_ns_buffer .= ' rdf:resource="&wiki;' . $expResource->getLocalName() . '"';
215
			} else {
216 14
				$uriValue = $this->makeAttributeValueString( $expResource->getUri() );
217 14
				$this->post_ns_buffer .= ' rdf:resource="' . $uriValue . '"';
218
			}
219
		}
220 14
		$this->post_ns_buffer .= "/>\n";
221 14
		if ( $isClassTypeProp ) {
222 5
			$this->requireDeclaration( $expResource, SMW_SERIALIZER_DECL_CLASS );
223
		}
224 14
	}
225
226
	/**
227
	 * Add a serialization of the given SMWExpResource to the output,
228
	 * assuming that an opening property tag is alerady there.
229
	 *
230
	 * @param $expResourceProperty SMWExpNsResource the property to use
231
	 * @param $expResource array of (SMWExpResource or SMWExpData)
232
	 * @param $indent string specifying a prefix for indentation (usually a sequence of tabs)
233
	 * @param $isClassTypeProp boolean whether the resource must be declared as a class
234
	 *
235
	 * @bug The $isClassTypeProp parameter is not properly taken into account.
236
	 * @bug Individual resources are not serialised properly.
237
	 */
238
	protected function serializeExpCollection( SMWExpNsResource $expResourceProperty, array $collection, $indent, $isClassTypeProp ) {
239
		$this->post_ns_buffer .= $indent . '<' . $expResourceProperty->getQName() . " rdf:parseType=\"Collection\">\n";
240
		foreach ( $collection as $expElement ) {
241
			if ( $expElement instanceof SMWExpData ) {
242
				$this->serializeNestedExpData( $expElement, $indent );
243
			} else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
244
				// FIXME: the below is not the right thing to do here
245
				//$this->serializeExpResource( $expResourceProperty, $expElement, $indent );
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
246
			}
247
			if ( $isClassTypeProp ) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
248
				// FIXME: $expResource is undefined
249
				//$this->requireDeclaration( $expResource, SMW_SERIALIZER_DECL_CLASS );
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
250
			}
251
		}
252
		$this->post_ns_buffer .= "$indent</" . $expResourceProperty->getQName() . ">\n";
253
	}
254
255
	/**
256
	 * Escape a string in the special form that is required for values in
257
	 * DTD entity declarations in XML. Namely, this require the percent sign
258
	 * to be replaced.
259
	 *
260
	 * @param $string string to be escaped
261
	 * @return string
262
	 */
263 14
	protected function makeValueEntityString( $string ) {
264 14
		return "'" . str_replace( '%', '&#37;', $string ) . "'";
265
	}
266
267
	/**
268
	 * Escape a string as required for using it in XML attribute values.
269
	 *
270
	 * @param $string string to be escaped
271
	 * @return string
272
	 */
273 14
	protected function makeAttributeValueString( $string ) {
274 14
		return str_replace( array( '&', '>', '<' ), array( '&amp;', '&gt;', '&lt;' ), $string );
275
	}
276
277
}
278