@@ -890,6 +890,7 @@ protected function getExampleIfAvailable(string $url, bool $useLocalToken = fals
890890 || stripos ($ response ['data ' ], '<result /> ' ) !== false
891891 || trim ($ response ['data ' ]) === '[] '
892892 || (stripos ($ url , 'format=tsv ' ) !== false && trim ($ response ['data ' ]) === 'No data available ' )
893+ || !preg_match ("/(json|xml|vnd.ms-excel)/ " , $ response ['headers ' ]['content-type ' ] ?? $ response ['headers ' ]['Content-Type ' ] ?? '' ) // Some ask for xml/json/tsv but return image/png, shouldn't be treated as xml
893894 ) {
894895 return '' ;
895896 }
@@ -900,7 +901,12 @@ protected function getExampleIfAvailable(string $url, bool $useLocalToken = fals
900901
901902 // Convert the XML responses into a JSON object and then encode it into a string. This is helpful for building schemas.
902903 if ($ format === 'xml ' ) {
903- $ body = json_encode ($ this ->convertExampleXmlToObject ($ body ));
904+ // Some plugins have invalid XML (e.g <North America>)
905+ try {
906+ $ body = json_encode ($ this ->convertExampleXmlToObject ($ body ));
907+ } catch (\Exception $ e ) {
908+ return '' ;
909+ }
904910 }
905911
906912 return $ body ;
@@ -935,7 +941,11 @@ protected function getCachedExampleResponseFile(string $pluginName, string $meth
935941 }
936942
937943 if (!$ rawResult && $ format === 'xml ' ) {
938- $ exampleContents = json_encode ($ this ->convertExampleXmlToObject ($ exampleContents ));
944+ try {
945+ $ exampleContents = json_encode ($ this ->convertExampleXmlToObject ($ exampleContents ));
946+ } catch (\Exception $ e ) {
947+ return '' ;
948+ }
939949 }
940950
941951 // Unless set otherwise, make sure that the example is around the max allowed characters. If raw, don't bother.
@@ -1013,7 +1023,8 @@ protected function getReportExampleUrlFromMetadata(string $pluginName, string $m
10131023 */
10141024 public function convertExampleXmlToObject (string $ xml ): array
10151025 {
1016- $ root = new \SimpleXMLElement ($ xml );
1026+
1027+ $ root = new \SimpleXMLElement ($ xml , LIBXML_NOERROR );
10171028
10181029 $ toArray = function (\SimpleXMLElement $ node ) use (&$ toArray ) {
10191030 if (!count ($ node ->children ()) && !count ($ node ->attributes ())) {
@@ -1132,7 +1143,7 @@ protected function determineResponses(array $rules, string $plugin, string $meth
11321143 $ exampleUrls = $ this ->getApplicableDemoExampleUrls ($ plugin , $ method , $ paramsData );
11331144 foreach ($ exampleUrls as $ type => $ url ) {
11341145 $ exampleValue = $ this ->getExampleIfAvailable ($ url );
1135- // If the example lookup failed, try making the same request locally using a temporary token.
1146+ // If the example lookup failed, try making the same request locally using a local token.
11361147 if (empty ($ exampleValue )) {
11371148 $ exampleValue = $ this ->getExampleIfAvailable ($ url , true );
11381149 }
@@ -1242,6 +1253,10 @@ protected function buildMediaTypePropertiesArray(string $format, string $example
12421253 $ mediaType = array_merge ($ mediaType , $ responseSchema );
12431254 }
12441255 if ($ format === 'tsv ' ) {
1256+ // Prevent accidental PHPDoc termination in generated annotation files.
1257+ $ exampleValue = preg_replace ('~(?<! \\\\)/ \\*~ ' , '\\/* ' , $ exampleValue ) ?? $ exampleValue ;
1258+ $ exampleValue = str_replace ('*/ ' , '*\/ ' , $ exampleValue );
1259+
12451260 // Escape quotes differently for the annotation examples
12461261 $ exampleValue = str_replace ('" ' , '"" ' , $ exampleValue );
12471262 $ mediaType [] = 'example=" ' . $ exampleValue . '" ' ;
@@ -1484,6 +1499,18 @@ public function buildPropertyAnnotationFromXmlExample(string $propName, array $v
14841499 {
14851500 $ type = 'object ' ;
14861501 $ originalValues = $ values ;
1502+ $ isList = !empty ($ values ) && array_keys ($ values ) === range (0 , count ($ values ) - 1 );
1503+ $ treatAsArray = $ propName !== 'row ' && $ isList ;
1504+ if ($ treatAsArray ) {
1505+ $ type = 'array ' ;
1506+ $ mergedValues = [];
1507+ foreach ($ values as $ value ) {
1508+ if (is_array ($ value )) {
1509+ $ mergedValues = array_merge ($ mergedValues , $ value );
1510+ }
1511+ }
1512+ $ values = $ mergedValues ;
1513+ }
14871514 if ($ propName === 'row ' ) {
14881515 $ type = 'array ' ;
14891516 // Merge the rows together to get as many properties as possible
@@ -1511,24 +1538,37 @@ public function buildPropertyAnnotationFromXmlExample(string $propName, array $v
15111538 continue ;
15121539 }
15131540
1514- // Special handling for XML attributes
1515- if ($ key === OpenApiDocs::OA_XML_ATTRIBUTES_TEMP_PROPERTY_NAME ) {
1516- $ hasAttributes = true ;
1517- $ childLines = array_merge ($ childLines , $ this ->buildXmlAttributeSchemaLines ($ value ));
1518- continue ;
1519- }
1520-
15211541 // Handle nested arrays
15221542 if (!is_string ($ key )) {
15231543 if (!is_array (reset ($ value ))) {
15241544 continue ;
15251545 }
15261546
15271547 $ keys = array_keys ($ value );
1528- $ key = reset ($ keys );
1548+ $ key = null ;
1549+ foreach ($ keys as $ candidate ) {
1550+ if (
1551+ $ candidate !== OpenApiDocs::OA_XML_ATTRIBUTES_TEMP_PROPERTY_NAME
1552+ && $ candidate !== OpenApiDocs::OA_XML_ATTRIBUTES_DEFAULT_KEY_NAME
1553+ ) {
1554+ $ key = $ candidate ;
1555+ break ;
1556+ }
1557+ }
1558+ $ key = $ key ?? reset ($ keys );
15291559 $ value = $ value [$ key ];
15301560 }
15311561
1562+ // Special handling for XML attributes (metadata-only)
1563+ if (
1564+ $ key === OpenApiDocs::OA_XML_ATTRIBUTES_TEMP_PROPERTY_NAME
1565+ || $ key === OpenApiDocs::OA_XML_ATTRIBUTES_DEFAULT_KEY_NAME
1566+ ) {
1567+ $ hasAttributes = true ;
1568+ $ childLines = array_merge ($ childLines , $ this ->buildXmlAttributeSchemaLines ($ value ));
1569+ continue ;
1570+ }
1571+
15321572 $ childLines [] = $ this ->buildPropertyAnnotationFromXmlExample ($ key , $ value );
15331573 }
15341574
@@ -1548,6 +1588,20 @@ public function buildPropertyAnnotationFromXmlExample(string $propName, array $v
15481588
15491589 $ childLines = ['@OA\Items ' => array_merge ($ itemProperties , $ childLines )];
15501590 }
1591+ if ($ treatAsArray ) {
1592+ $ itemProperties = [
1593+ 'type="object", ' ,
1594+ sprintf ('@OA\Xml(name="%s"), ' , $ propName ),
1595+ 'additionalProperties=true, ' ,
1596+ ];
1597+
1598+ $ originalKeys = array_keys ($ originalValues );
1599+ if (!is_string (reset ($ originalKeys )) && !is_string (reset ($ values )) && !$ hasAttributes ) {
1600+ $ itemProperties = ['type="string" ' ];
1601+ }
1602+
1603+ $ childLines = ['@OA\Items ' => array_merge ($ itemProperties , $ childLines )];
1604+ }
15511605
15521606 return ['@OA\Property ' => array_merge ($ propertyLines , $ childLines )];
15531607 }
@@ -1822,12 +1876,12 @@ public function compileOperationLines(string $path, string $opId, string $plugin
18221876 $ code = $ response ['code ' ];
18231877 $ codeFormatted = is_numeric ($ code ) ? (string )$ code : '" ' . $ code . '" ' ;
18241878 $ description = !empty ($ response ['description ' ]) && strpos ($ response ['description ' ], 'Example links: [ ' ) !== false
1825- ? ', description=" ' . $ response ['description ' ] . '" ' : '' ;
1879+ ? ', description=" ' . $ this -> normaliseDescriptionText ( $ response ['description ' ]) . '" ' : '' ;
18261880 $ operationValuesMap [] = '@OA\Response(response= ' . $ codeFormatted . $ description . ', ref=" ' . $ response ['ref ' ] . '") ' ;
18271881 } else {
18281882 $ responsePropertyArray = [
18291883 'response=200 ' ,
1830- 'description=" ' . ($ response ['description ' ] ?? 'OK ' ) . '" ' ,
1884+ 'description=" ' . $ this -> normaliseDescriptionText ($ response ['description ' ] ?? 'OK ' ) . '" ' ,
18311885 ];
18321886 if (!empty ($ response ['schema ' ])) {
18331887 $ responsePropertyArray = array_merge ($ responsePropertyArray , $ response ['schema ' ]);
0 commit comments