Christian Fraß commited on 2021-03-03 01:35:40
              Zeige 5 geänderte Dateien mit 201 Einfügungen und 48 Löschungen.
            
| ... | ... | 
                      @@ -8,6 +8,13 @@ import {repositories.concept} from './repository-concept';
                     | 
                  
| 8 | 8 | 
                        */  | 
                    
| 9 | 9 | 
                         | 
                    
| 10 | 10 | 
                         | 
                    
| 11 | 
                        +function syntaxerror() : void  | 
                    |
| 12 | 
                        +{
                       | 
                    |
| 13 | 
                        +	console.error("-- wrong syntax");
                       | 
                    |
| 14 | 
                        + process.exit(1);  | 
                    |
| 15 | 
                        +}  | 
                    |
| 16 | 
                        +  | 
                    |
| 17 | 
                        +  | 
                    |
| 11 | 18 | 
                        /**  | 
                    
| 12 | 19 | 
                        */  | 
                    
| 13 | 20 | 
                        async function main(args : Array<string>) : Promise<void>  | 
                    
| ... | ... | 
                      @@ -24,13 +31,11 @@ async function main(args : Array<string>) : Promise<void>  | 
                  
| 24 | 31 | 
                        process.exit(0);  | 
                    
| 25 | 32 | 
                        break;  | 
                    
| 26 | 33 | 
                        }  | 
                    
| 27 | 
                        - case "feed":  | 
                    |
| 34 | 
                        + case "show":  | 
                    |
| 28 | 35 | 
                         		{
                       | 
                    
| 29 | 
                        - const content : string = await helpers.misc.stdin();  | 
                    |
| 30 | 
                        - const data : any = JSON.parse(content);  | 
                    |
| 31 | 
                        - const concept_thing : any = data;  | 
                    |
| 32 | 
                        - const concept_id : int = await services.concept.suck(concept_thing);  | 
                    |
| 33 | 
                        - console.info(concept_id);  | 
                    |
| 36 | 
                        + const concept_id = parseInt(args.shift());  | 
                    |
| 37 | 
                        + const concept_entity : entities.concept = await services.concept.get(concept_id);  | 
                    |
| 38 | 
                        + console.info(JSON.stringify(concept_entity, undefined, "\t"));  | 
                    |
| 34 | 39 | 
                        process.exit(0);  | 
                    
| 35 | 40 | 
                        break;  | 
                    
| 36 | 41 | 
                        }  | 
                    
| ... | ... | 
                      @@ -38,7 +43,7 @@ async function main(args : Array<string>) : Promise<void>  | 
                  
| 38 | 43 | 
                         		{
                       | 
                    
| 39 | 44 | 
                        const language_from : string = args.shift();  | 
                    
| 40 | 45 | 
                        const language_to : string = args.shift();  | 
                    
| 41 | 
                        - const part : string = args.shift();  | 
                    |
| 46 | 
                        +			const part : string = args.join(" "); args = [];
                       | 
                    |
| 42 | 47 | 
                        const result = await services.concept.get_translations  | 
                    
| 43 | 48 | 
                        (  | 
                    
| 44 | 49 | 
                        language_from,  | 
                    
| ... | ... | 
                      @@ -55,9 +60,9 @@ async function main(args : Array<string>) : Promise<void>  | 
                  
| 55 | 60 | 
                        (  | 
                    
| 56 | 61 | 
                         							"[{{language_from}}] {{value_from}} ~ [{{language_to}}] {{value_to}}",
                       | 
                    
| 57 | 62 | 
                         							{
                       | 
                    
| 58 | 
                        - "language_from": language_from,  | 
                    |
| 63 | 
                        + "language_from": entry["language_from"],  | 
                    |
| 59 | 64 | 
                        "value_from": entry["value_from"],  | 
                    
| 60 | 
                        - "language_to": language_to,  | 
                    |
| 65 | 
                        + "language_to": entry["language_to"],  | 
                    |
| 61 | 66 | 
                        "value_to": entry["value_to"],  | 
                    
| 62 | 67 | 
                        }  | 
                    
| 63 | 68 | 
                        )  | 
                    
| ... | ... | 
                      @@ -74,13 +79,86 @@ async function main(args : Array<string>) : Promise<void>  | 
                  
| 74 | 79 | 
                        process.exit(0);  | 
                    
| 75 | 80 | 
                        break;  | 
                    
| 76 | 81 | 
                        }  | 
                    
| 77 | 
                        - case "show":  | 
                    |
| 82 | 
                        + case "search":  | 
                    |
| 78 | 83 | 
                         		{
                       | 
                    
| 79 | 
                        - const concept_id = parseInt(args.shift());  | 
                    |
| 80 | 
                        - const concept_entity : entities.concept = await services.concept.get(concept_id);  | 
                    |
| 81 | 
                        - console.info(JSON.stringify(concept_entity, undefined, "\t"));  | 
                    |
| 84 | 
                        +			const part : string = args.join(" "); args = [];
                       | 
                    |
| 85 | 
                        + const result = await services.concept.search(part);  | 
                    |
| 86 | 
                        + result.forEach  | 
                    |
| 87 | 
                        + (  | 
                    |
| 88 | 
                        + (entry) =>  | 
                    |
| 89 | 
                        +				{
                       | 
                    |
| 90 | 
                        + console.info  | 
                    |
| 91 | 
                        + (  | 
                    |
| 92 | 
                        + helpers.string.coin  | 
                    |
| 93 | 
                        + (  | 
                    |
| 94 | 
                        +							"{{id}} | {{type}} | {{description}} | {{tags}} | {{translations}}",
                       | 
                    |
| 95 | 
                        +							{
                       | 
                    |
| 96 | 
                        + "id": entry["id"].toFixed(),  | 
                    |
| 97 | 
                        + "type": entry["type"],  | 
                    |
| 98 | 
                        + "description": (entry["description"] ?? '-'),  | 
                    |
| 99 | 
                        +								"tags": entry["tags"].join(","),
                       | 
                    |
| 100 | 
                        +								"translations": entry["translations"].map(foo => (foo["language"] + ":" + foo["value"])).join(","),
                       | 
                    |
| 101 | 
                        + }  | 
                    |
| 102 | 
                        + )  | 
                    |
| 103 | 
                        + );  | 
                    |
| 104 | 
                        + }  | 
                    |
| 105 | 
                        + );  | 
                    |
| 106 | 
                        + process.exit(0);  | 
                    |
| 107 | 
                        + break;  | 
                    |
| 108 | 
                        + }  | 
                    |
| 109 | 
                        + case "feed":  | 
                    |
| 110 | 
                        +		{
                       | 
                    |
| 111 | 
                        + const content : string = await helpers.misc.stdin();  | 
                    |
| 112 | 
                        + const data : any = JSON.parse(content);  | 
                    |
| 113 | 
                        + const concept_thing : any = data;  | 
                    |
| 114 | 
                        + const concept_id : int = await services.concept.suck(concept_thing);  | 
                    |
| 115 | 
                        + console.info(concept_id);  | 
                    |
| 116 | 
                        + process.exit(0);  | 
                    |
| 82 | 117 | 
                        break;  | 
                    
| 83 | 118 | 
                        }  | 
                    
| 119 | 
                        + case "new":  | 
                    |
| 120 | 
                        +		{
                       | 
                    |
| 121 | 
                        + if (args.length < 1)  | 
                    |
| 122 | 
                        +			{
                       | 
                    |
| 123 | 
                        + syntaxerror();  | 
                    |
| 124 | 
                        + }  | 
                    |
| 125 | 
                        + else  | 
                    |
| 126 | 
                        +			{
                       | 
                    |
| 127 | 
                        + const type : string = args.shift();  | 
                    |
| 128 | 
                        + const tags : Array<string> = (  | 
                    |
| 129 | 
                        + (args.length >= 1)  | 
                    |
| 130 | 
                        +					? args.shift().split(",")
                       | 
                    |
| 131 | 
                        + : []  | 
                    |
| 132 | 
                        + );  | 
                    |
| 133 | 
                        + const description : string = (  | 
                    |
| 134 | 
                        + (args.length >= 1)  | 
                    |
| 135 | 
                        + ? args.shift()  | 
                    |
| 136 | 
                        + : null  | 
                    |
| 137 | 
                        + );  | 
                    |
| 138 | 
                        + const concept_thing : any =  | 
                    |
| 139 | 
                        +				{
                       | 
                    |
| 140 | 
                        + "type": type,  | 
                    |
| 141 | 
                        + "description": description,  | 
                    |
| 142 | 
                        + "tags": tags,  | 
                    |
| 143 | 
                        + "translations": [],  | 
                    |
| 144 | 
                        + };  | 
                    |
| 145 | 
                        + const concept_id : int = await services.concept.suck(concept_thing);  | 
                    |
| 146 | 
                        + console.info(concept_id);  | 
                    |
| 147 | 
                        + process.exit(0);  | 
                    |
| 148 | 
                        + }  | 
                    |
| 149 | 
                        + break;  | 
                    |
| 150 | 
                        + }  | 
                    |
| 151 | 
                        + case "set":  | 
                    |
| 152 | 
                        +		{
                       | 
                    |
| 153 | 
                        + if (args.length < 1)  | 
                    |
| 154 | 
                        +			{
                       | 
                    |
| 155 | 
                        + syntaxerror();  | 
                    |
| 156 | 
                        + }  | 
                    |
| 157 | 
                        + else  | 
                    |
| 158 | 
                        +			{
                       | 
                    |
| 159 | 
                        + const concept_id : int = parseInt(args.shift());  | 
                    |
| 160 | 
                        + }  | 
                    |
| 161 | 
                        + }  | 
                    |
| 84 | 162 | 
                        default:  | 
                    
| 85 | 163 | 
                         		{
                       | 
                    
| 86 | 164 | 
                         			console.error("unhandled command: " + command);
                       | 
                    
| ... | ... | 
                      @@ -9,6 +9,7 @@ namespace repositories  | 
                  
| 9 | 9 | 
                         		{
                       | 
                    
| 10 | 10 | 
                        get_translations : (language_from : string, language_to : string, part : string)=>Promise<Array<type_row>>;  | 
                    
| 11 | 11 | 
                        export : ()=>Promise<Array<type_row>>;  | 
                    
| 12 | 
                        + search : (part : string)=>Promise<Array<type_row>>;  | 
                    |
| 12 | 13 | 
                        }  | 
                    
| 13 | 14 | 
                        ) =  | 
                    
| 14 | 15 | 
                         	{
                       | 
                    
| ... | ... | 
                      @@ -67,21 +68,25 @@ namespace repositories  | 
                  
| 67 | 68 | 
                        };  | 
                    
| 68 | 69 | 
                        return Promise.resolve<entities.concept>(concept_entity);  | 
                    
| 69 | 70 | 
                        },  | 
                    
| 70 | 
                        - "get_translations": function (language_from : string, language_to : string, part : string) : Promise<Array<type_row>>  | 
                    |
| 71 | 
                        + "get_translations": function (language_from, language_to, part)  | 
                    |
| 71 | 72 | 
                         		{
                       | 
                    
| 72 | 73 | 
                        return helpers.database.query_get_named  | 
                    
| 73 | 74 | 
                        (  | 
                    
| 74 | 75 | 
                        "concept.get_translations",  | 
                    
| 75 | 76 | 
                         				{
                       | 
                    
| 76 | 
                        - "language_value_from": language_from,  | 
                    |
| 77 | 
                        - "language_value_to": language_to,  | 
                    |
| 77 | 
                        + "language_from": language_from,  | 
                    |
| 78 | 
                        + "language_to": language_to,  | 
                    |
| 78 | 79 | 
                         					"part": part.replace(new RegExp("-", "g"), "%"),
                       | 
                    
| 79 | 80 | 
                        }  | 
                    
| 80 | 81 | 
                        );  | 
                    
| 81 | 82 | 
                        },  | 
                    
| 82 | 83 | 
                        "export": function () : Promise<Array<type_row>>  | 
                    
| 83 | 84 | 
                         		{
                       | 
                    
| 84 | 
                        -			return helpers.database.query_get_named("concept.export");
                       | 
                    |
| 85 | 
                        +			return helpers.database.query_get_named("concept.dump", {"part": null});
                       | 
                    |
| 86 | 
                        + },  | 
                    |
| 87 | 
                        + "search": function (part) : Promise<Array<type_row>>  | 
                    |
| 88 | 
                        +		{
                       | 
                    |
| 89 | 
                        +			return helpers.database.query_get_named("concept.dump", {"part": part.replace(new RegExp("-", "g"), "%")});
                       | 
                    |
| 85 | 90 | 
                        },  | 
                    
| 86 | 91 | 
                        };  | 
                    
| 87 | 92 | 
                         | 
                    
| ... | ... | 
                      @@ -67,6 +67,41 @@ namespace services.concept  | 
                  
| 67 | 67 | 
                        }  | 
                    
| 68 | 68 | 
                         | 
                    
| 69 | 69 | 
                         | 
                    
| 70 | 
                        + /**  | 
                    |
| 71 | 
                        + */  | 
                    |
| 72 | 
                        + function parse_tags(  | 
                    |
| 73 | 
                        + tags_raw : string  | 
                    |
| 74 | 
                        + ) : Array<string>  | 
                    |
| 75 | 
                        +	{
                       | 
                    |
| 76 | 
                        + return (  | 
                    |
| 77 | 
                        + (tags_raw === null)  | 
                    |
| 78 | 
                        + ? []  | 
                    |
| 79 | 
                        +			: tags_raw.split(",")
                       | 
                    |
| 80 | 
                        + );  | 
                    |
| 81 | 
                        + }  | 
                    |
| 82 | 
                        +  | 
                    |
| 83 | 
                        +  | 
                    |
| 84 | 
                        + /**  | 
                    |
| 85 | 
                        + */  | 
                    |
| 86 | 
                        + function parse_translations  | 
                    |
| 87 | 
                        + (  | 
                    |
| 88 | 
                        + translations_raw : string  | 
                    |
| 89 | 
                        +	) : Array<{id : int; language : string; value : string;}>
                       | 
                    |
| 90 | 
                        +	{
                       | 
                    |
| 91 | 
                        +		let result : Array<{id : int; language : string; value : string;}> = [];
                       | 
                    |
| 92 | 
                        +		const parts : Array<string> = translations_raw.split(",")
                       | 
                    |
| 93 | 
                        + parts.forEach  | 
                    |
| 94 | 
                        + (  | 
                    |
| 95 | 
                        + (part) =>  | 
                    |
| 96 | 
                        +			{
                       | 
                    |
| 97 | 
                        +				const [id, language, value] : Array<string> = part.split(":", 3);
                       | 
                    |
| 98 | 
                        +				result.push({"id": parseInt(id), "language": language, "value": value});
                       | 
                    |
| 99 | 
                        + }  | 
                    |
| 100 | 
                        + );  | 
                    |
| 101 | 
                        + return result;  | 
                    |
| 102 | 
                        + }  | 
                    |
| 103 | 
                        +  | 
                    |
| 104 | 
                        +  | 
                    |
| 70 | 105 | 
                        /**  | 
                    
| 71 | 106 | 
                        */  | 
                    
| 72 | 107 | 
                        export function get_translations  | 
                    
| ... | ... | 
                      @@ -89,33 +124,45 @@ namespace services.concept  | 
                  
| 89 | 124 | 
                        */  | 
                    
| 90 | 125 | 
                        export async function export_  | 
                    
| 91 | 126 | 
                        (  | 
                    
| 92 | 
                        -	) : Promise<Array<{id : int; type : string; description : string; tags : Array<string>; translations : {[language : string] : Array<string>}}>>
                       | 
                    |
| 93 | 
                        -	{
                       | 
                    |
| 94 | 
                        - const parse_tags = function (tags_raw : string) : Array<string>  | 
                    |
| 127 | 
                        +	) : Promise<Array<{id : int; type : string; description : string; tags : Array<string>; translations : Array<{id : int; language : string; value : string;}>;}>>
                       | 
                    |
| 95 | 128 | 
                         	{
                       | 
                    
| 96 | 129 | 
                        return (  | 
                    
| 97 | 
                        - (tags_raw === null)  | 
                    |
| 98 | 
                        - ? []  | 
                    |
| 99 | 
                        -				: tags_raw.split(",")
                       | 
                    |
| 100 | 
                        - );  | 
                    |
| 101 | 
                        - };  | 
                    |
| 102 | 
                        -		const parse_translations = function (translations_raw : string) : {[language : string] : Array<string>}
                       | 
                    |
| 103 | 
                        -		{
                       | 
                    |
| 104 | 
                        -			let result : {[language : string] : Array<string>} = {};
                       | 
                    |
| 105 | 
                        -			const parts : Array<string> = translations_raw.split(",")
                       | 
                    |
| 106 | 
                        - parts.forEach  | 
                    |
| 130 | 
                        + repositories.concept.export()  | 
                    |
| 131 | 
                        + .then  | 
                    |
| 107 | 132 | 
                        (  | 
                    
| 108 | 
                        - (part) =>  | 
                    |
| 133 | 
                        + rows => Promise.resolve  | 
                    |
| 134 | 
                        + (  | 
                    |
| 135 | 
                        + rows.map  | 
                    |
| 136 | 
                        + (  | 
                    |
| 137 | 
                        + (row) => (  | 
                    |
| 109 | 138 | 
                         							{
                       | 
                    
| 110 | 
                        -					const [language, value] : Array<string> = part.split(":", 2);
                       | 
                    |
| 111 | 
                        - if (! result.hasOwnProperty(language)) result[language] = [];  | 
                    |
| 112 | 
                        - result[language].push(value);  | 
                    |
| 139 | 
                        + "id": row["id"],  | 
                    |
| 140 | 
                        + "type": row["type"],  | 
                    |
| 141 | 
                        + "description": row["description"],  | 
                    |
| 142 | 
                        + "tags": parse_tags(row["tags"]),  | 
                    |
| 143 | 
                        + "translations": parse_translations(row["translations"]),  | 
                    |
| 113 | 144 | 
                        }  | 
                    
| 114 | 
                        - );  | 
                    |
| 115 | 
                        - return result;  | 
                    |
| 116 | 
                        - };  | 
                    |
| 117 | 
                        - const rows : Array<type_row> = await repositories.concept.export();  | 
                    |
| 118 | 
                        - return Promise.resolve<any>(  | 
                    |
| 145 | 
                        + )  | 
                    |
| 146 | 
                        + )  | 
                    |
| 147 | 
                        + )  | 
                    |
| 148 | 
                        + )  | 
                    |
| 149 | 
                        + )  | 
                    |
| 150 | 
                        + }  | 
                    |
| 151 | 
                        +  | 
                    |
| 152 | 
                        +  | 
                    |
| 153 | 
                        + /**  | 
                    |
| 154 | 
                        + */  | 
                    |
| 155 | 
                        + export function search  | 
                    |
| 156 | 
                        + (  | 
                    |
| 157 | 
                        + part : string  | 
                    |
| 158 | 
                        +	) : Promise<Array<{id : int; type : string; description : string; tags : Array<string>; translations : Array<{id : int; language : string; value : string;}>;}>>
                       | 
                    |
| 159 | 
                        +	{
                       | 
                    |
| 160 | 
                        + return (  | 
                    |
| 161 | 
                        + repositories.concept.search(part)  | 
                    |
| 162 | 
                        + .then  | 
                    |
| 163 | 
                        + (  | 
                    |
| 164 | 
                        + rows => Promise.resolve  | 
                    |
| 165 | 
                        + (  | 
                    |
| 119 | 166 | 
                        rows.map  | 
                    
| 120 | 167 | 
                        (  | 
                    
| 121 | 168 | 
                        (row) => (  | 
                    
| ... | ... | 
                      @@ -125,7 +172,10 @@ namespace services.concept  | 
                  
| 125 | 172 | 
                        "description": row["description"],  | 
                    
| 126 | 173 | 
                        "tags": parse_tags(row["tags"]),  | 
                    
| 127 | 174 | 
                        "translations": parse_translations(row["translations"]),  | 
                    
| 128 | 
                        - })  | 
                    |
| 175 | 
                        + }  | 
                    |
| 176 | 
                        + )  | 
                    |
| 177 | 
                        + )  | 
                    |
| 178 | 
                        + )  | 
                    |
| 129 | 179 | 
                        )  | 
                    
| 130 | 180 | 
                        )  | 
                    
| 131 | 181 | 
                        }  | 
                    
| ... | ... | 
                      @@ -1,8 +1,9 @@  | 
                  
| 1 | 1 | 
                        SELECT  | 
                    
| 2 | 2 | 
                        x1.id AS id,  | 
                    
| 3 | 3 | 
                        MIN(x2.value) AS type,  | 
                    
| 4 | 
                        + MIN(x1.description) AS description,  | 
                    |
| 4 | 5 | 
                        GROUP_CONCAT(DISTINCT x4.value) AS tags,  | 
                    
| 5 | 
                        - GROUP_CONCAT(x6.value || ':' || x5.value) AS translations  | 
                    |
| 6 | 
                        + GROUP_CONCAT(x5.id || ':' || x6.value || ':' || x5.value) AS translations  | 
                    |
| 6 | 7 | 
                        FROM  | 
                    
| 7 | 8 | 
                        concepts AS x1  | 
                    
| 8 | 9 | 
                        LEFT OUTER JOIN types AS x2 ON (x1.type_id = x2.id)  | 
                    
| ... | ... | 
                      @@ -10,6 +11,12 @@ FROM  | 
                  
| 10 | 11 | 
                        LEFT OUTER JOIN tags AS x4 ON (x3.tag_id = x4.id)  | 
                    
| 11 | 12 | 
                        LEFT OUTER JOIN concept_translations AS x5 ON (x1.id = x5.concept_id)  | 
                    
| 12 | 13 | 
                        LEFT OUTER JOIN languages AS x6 ON (x5.language_id = x6.id)  | 
                    
| 14 | 
                        +WHERE  | 
                    |
| 15 | 
                        + (  | 
                    |
| 16 | 
                        + (:part IS NULL)  | 
                    |
| 17 | 
                        + OR  | 
                    |
| 18 | 
                        + (x5.value LIKE :part)  | 
                    |
| 19 | 
                        + )  | 
                    |
| 13 | 20 | 
                        GROUP BY  | 
                    
| 14 | 21 | 
                        x1.id  | 
                    
| 15 | 22 | 
                        ;  | 
                    
| ... | ... | 
                      @@ -1,16 +1,29 @@  | 
                  
| 1 | 1 | 
                        SELECT  | 
                    
| 2 | 
                        - x.concept_id AS concept_id,  | 
                    |
| 3 | 
                        - x.value AS value_from,  | 
                    |
| 4 | 
                        - y.value AS value_to  | 
                    |
| 2 | 
                        + x1.concept_id AS concept_id,  | 
                    |
| 3 | 
                        + x2.value AS language_from,  | 
                    |
| 4 | 
                        + x1.value AS value_from,  | 
                    |
| 5 | 
                        + y2.value AS language_to,  | 
                    |
| 6 | 
                        + y1.value AS value_to  | 
                    |
| 5 | 7 | 
                        FROM  | 
                    
| 6 | 
                        - concept_translations AS x INNER JOIN concept_translations AS y ON (x.concept_id = y.concept_id)  | 
                    |
| 8 | 
                        + concept_translations AS x1  | 
                    |
| 9 | 
                        + INNER JOIN languages AS x2 ON (x1.language_id = x2.id)  | 
                    |
| 10 | 
                        + INNER JOIN concept_translations AS y1 ON ((x1.id <> y1.id) AND (x1.concept_id = y1.concept_id))  | 
                    |
| 11 | 
                        + INNER JOIN languages AS y2 ON (y1.language_id = y2.id)  | 
                    |
| 7 | 12 | 
                        WHERE  | 
                    
| 8 | 13 | 
                        (  | 
                    
| 9 | 
                        - (x.language_id = (SELECT id FROM languages WHERE (value = :language_value_from)))  | 
                    |
| 14 | 
                        + (  | 
                    |
| 15 | 
                        + (:language_from = '_')  | 
                    |
| 16 | 
                        + OR  | 
                    |
| 17 | 
                        + (x2.value = :language_from)  | 
                    |
| 18 | 
                        + )  | 
                    |
| 10 | 19 | 
                        AND  | 
                    
| 11 | 
                        - (y.language_id = (SELECT id FROM languages WHERE (value = :language_value_to)))  | 
                    |
| 20 | 
                        + (  | 
                    |
| 21 | 
                        + (:language_to = '_')  | 
                    |
| 22 | 
                        + OR  | 
                    |
| 23 | 
                        + (y2.value = :language_to)  | 
                    |
| 24 | 
                        + )  | 
                    |
| 12 | 25 | 
                        AND  | 
                    
| 13 | 
                        - (x.value LIKE :part)  | 
                    |
| 26 | 
                        + (x1.value LIKE :part)  | 
                    |
| 14 | 27 | 
                        )  | 
                    
| 15 | 28 | 
                        ;  | 
                    
| 16 | 29 | 
                         | 
                    
| 17 | 30 |