विकिपीडिया bhwiki https://bh.wikipedia.org/wiki/%E0%A4%AE%E0%A5%81%E0%A4%96%E0%A5%8D%E0%A4%AF_%E0%A4%AA%E0%A4%A8%E0%A5%8D%E0%A4%A8%E0%A4%BE MediaWiki 1.47.0-wmf.13 first-letter मीडिया विशेष वार्तालाप प्रयोगकर्ता प्रयोगकर्ता वार्ता विकिपीडिया विकिपीडिया वार्ता चित्र चित्र वार्ता मीडियाविकि मीडियाविकि वार्ता टेम्पलेट टेम्पलेट वार्ता मदद मदद वार्ता श्रेणी श्रेणी वार्ता TimedText TimedText talk Module Module talk Event Event talk नेवादा 0 2029 803299 773317 2026-07-29T16:06:48Z SM7Bot 7808 /* top */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803299 wikitext text/x-wiki '''नेवादा''' [[संयुक्त राज्य अमेरिका]] के एगो [[राज्य (प्रांत)|राज्य]] बा। ई लेख अमेरिकी राज्य के बारे में बा। अन्य उपयोग खातीर, नेवादा (बहुविकल्पी) देखल जाव। "सिल्वर स्टेट" ई पुनर्निर्देश करता। अन्य उपयोग के खातीर, सिल्वर स्टेट (बहुविकल्पी) देखल जाव। [C] एकर सीमा उत्तर-पश्चिम में ओरेगन, उत्तर-पूर्व में इडाहो, पश्चिम में कैलिफ़ोर्निया, दक्षिण-पूर्व में एरिज़ोना औरी पूरब में यूटा से लागेला । नेवादा सातवा स्थान पर बा। नेवादा अमेरिकी राज्य में 7वां सबसे व्यापक, 32वां सबसे अधिक आबादी वाला और 9वां सबसे कम घनी आबादी वाला राज्य ह। नेवादा के लगभग तीन-चौथाई लोग क्लार्क काउंटी में रहेला, जउना में लास वेगास-पैराडाइज़ महानगरीय क्षेत्र शामिल बा, [5] जेमें राज्य के चार सबसे बड़ सम्मिलित सहर में से तीन गो शामिल बा। [6] नेवादा के राजधानी कार्सन सिटी ह। लास वेगास राज्य के सबसे बड़ सहर ह। नेवादा के अपना इतिहास औरी अर्थव्यवस्था में चांदी के महत्व के कारण आधिकारिक तौर पर "सिल्वर स्टेट" के रूप में जानल जाला। इ "बैटल बॉर्न स्टेट" के रूप में भी जानल जाला कहेकी एमे गृहयुद्ध के दौरान राज्य का दर्जा हासिल कइल गइल बा ("बैटल बॉर्न" शब्द एमे राज्य ध्वज पर भी दिखाई देवेला); अब्राहम लिंकन के अध्यक्षता के कारण, कॉमस्टॉक लॉड द्वारा उ समय उत्पन्न चांदी के अयस्क में लगभग $400 मिलियन की मौद्रिक सहायता के द्वारा नव सम्मानित राज्य के समर्थन से संघ के ज्यादा लाभ भइल रहे। [उद्धरण वांछित] एके इहे नाम के मूल पौधा के खातिर "सेजब्रश स्टेट" के रूप में भी जानल जाला ; औरी "ऋषि-मुर्गी राज्य" के रूप में।[7] स्पैनिश में नाम के अर्थ "बर्फीला" होला, जावन सिएरा नेवादा पर्वत श्रृंखला के साथे नेवादा के छोट ओवरलैप के जिक्र करेला ; हालाँकि, नेवादा के शेष भाग बड़ पैमाना पे रेगिस्तानी और अर्ध-शुष्क बा, एके अधिकतर भाग ग्रेट बेसिन के भीतर बा। ग्रेट बेसिन के दक्खिन के क्षेत्र मोजावे रेगिस्तान के भीतर बा, जबकि ताहो झील और सिएरा नेवादा पश्चिमी किनारे पर स्थित बा। राज्य की लगभग 86% भूमि के प्रबंधन अमिरिकी संघीय सरकार के विभिन्न न्यायालय के द्वारा कइल जाला , नागरिक और सैन्य दुनु। [8] पाइयूट, शोशोन औरी वाशू जनजाति के अमिरिकी भारतीय अब नेवादा में निवास करेला। इ क्षेत्र का पता लगावे वाला पहले यूरोपीय स्पेनिश रहे। उ लोग इ क्षेत्र के नेवादा (बर्फीला) कहेला काहेकी जाड़ा के दिन में पहाड़ पे बरफ जम जात रहे, जौन स्पेन के सिएरा नेवादा के जईसन रहे। इ क्षेत्र न्यू स्पेन के वायसरायल्टी के भीतर अल्टा कैलिफ़ोर्निया के क्षेत्र के हिस्सा बनल, जेमे 1821 में मेक्सिको के रूप में स्वतंत्रता प्राप्त भइल। मैक्सिकन-अमेरिकी युद्ध में जीत के बाद संयुक्त राज्य अमेरिका  1848 में इस क्षेत्र पर कब्ज़ा कर लिहल औरी 1850 में इ यूटा क्षेत्र के हिस्सा के रूप में सामिल कर लिहल गइल। 1859 में कॉम्स्टॉक लॉड में चांदी की खोज के कारण जनसंख्या में उछाल आइल जौन एगो प्रेरणा बन गइल। 1861 में पक्षिमी यूटा क्षेत्र से नेवादा क्षेत्र के निर्माण। नेवादा 31 अक्टूबर, 1864 के दूगो राज्य में से दूसरा के रूप में 36वां राज्य बन गइल। गृहयुद्ध के समय संघ में जोड़ल गइल। (पहिला वेस्ट वर्जीनिया रहे)। [9] नेवादा आपन स्वतंत्रतावादी कानून के खातिर जानल जाला। 1940 में, 110,000 से बेसी लोगन के आबादी के साथे, नेवादा अब तक का सबसे कम आबादी वाला राज्य रहे, जबकि अगीला सबसे कम आबादी वाला राज्य, व्योमिंग के आधा से भी कम आबादी रहे। [10] हालाँकि, वैध जुए औरी उदार वियाह औरी तलाक कानून  नेवादा के 20वीं सदी में एगो  परमुख पर्यटन अस्थल में बदल दिहल गइल। 11][12] नेवादा एकमात्र अमेरिकी राज्य बा जहां वेश्यावृत्ति के कानूनी अधिकार मिलल बा, हालांकि एके सबसे अधिक आबादी वाला क्षेत्र - क्लार्क काउंटी (लास वेगास), वाशो काउंटी (रेनो) और कार्सन सिटी (जौन एगो स्वतंत्र सहर के रूप में बा) में इ अवैध बा। काउनो भी काउंटी के सीमा के भीतर नइखे)। पर्यटन उद्योग नेवादा का सबसे बड़ा नियोक्ता बनल बा। [13] खनन अर्थव्यवस्था के एगो महत्वपूर्ण क्षेत्र के रूप में जारी बा: नेवादा दुनिया में सोना के चौथा सबसे बड़ा उत्पादक ह। [14] नेवादा सबसे शुष्क राज्य ह, औरी समय के साथ, औरी जलवायु परिवर्तन से प्रभावित होके, नेवादा में सूखा आवृत्ति और गंभीरता में बढ़ रहल बा, [15] जैना से नेवादा के जल सुरक्षा पर औरी दबाव पड़ रहल बा। शब्द-साधन "नेवादा" नाम स्पैनिश विशेषण नेवादा ([neˈβaða]) से आइल  बा , जेकर अर्थ हवे "बर्फ से तोपल " या "बर्फीला"।[16] राज्य का नाम नेवादा क्षेत्र से लिहल गइल बा, जेकर नाम सिएरा नेवादा के नाम पर रखल गइल बा। नेवादावासी दूसरे शब्दांश के उच्चारण "सेब" के "ए" (/nɪˈvædə/) के साथ करेला , जबकि राज्य के बाहर के कुछ लोग एकर  उच्चारण "हथेली" के "ए" (/nɪˈvɑːdə/) के साथे करेला। [18] हालांकि बाद वाला उच्चारण के गुणवत्ता, लेकिन लंबाई ना, स्पेनिश उच्चारण के करीब बा (स्पेनिश /ए/ खुला केंद्रीय ह [ए],[19] जबकि अमेरिकी अंग्रेजी /ɑː/ पीछे से भिन्न होला  [ɑː] से सेंट्रल [äː]),[20] इ नेवादावासि द्वारा इस्तेमाल कइल जाये वाला उच्चारण ना ह ।राज्य विधानसभा सदस्य हैरी मोर्टेंसन  नेवादा के वैकल्पिक उच्चारण के मान्यता देवे खातिर एगो विधेयक के प्रस्ताव रखले,[21] हालांकि इ विधेयक के अधिकांश विधायक लाेगिन के समर्थन नइखे मिलल औरी एकरा कबो वोट नइखे मिलल। नेवादन उच्चारण राज्य विधायिका के द्वारा उपयोग कइल जाए वाला उच्चारण ह। एक समय में, राज्य के आधिकारिक पर्यटन संगठन, ट्रैवेलनेवादा  राज्य के नाम के "नेवादा" के रूप में शैलीबद्ध काईल गइल रहे, जेमें स्थानीय रूप से पसंदीदा उच्चारण के इंगित करे वाला ए पर एक संक्षिप्त नाम रहे, [22] जौन 2007 तक लाइसेंस प्लेट डिजाइन के रूप में भी उपलब्ध रहे। [23] इतिहास मुख्य लेख: नेवादा के इतिहास अधिक जानकारी: लास वेगास के इतिहास स्वदेशी इतिहास यूरोपीय लोगन के आगमन से पहिले, सबसे शुरुआती निवासी स्वदेशी जनजातियो रहे जेमें गोशूट, दक्षिणी पाइयूट लोग, मोहावे लोग और वाशिसिव (वाशू लोग) शामिल रहे।[24][25] 1861 से पहीले मुख्य लेख: कैलिफ़ोर्निया § इतिहास, और अल्टा कैलिफ़ोर्निया अधिक जानकारी: कोर्डोबा के संधि, मैक्सिकन साम्राज्य के स्वतंत्रता के घोषणा, पहीला मैक्सिकन साम्राज्य, मैक्सिको के अनंतिम सरकार, पहीला मैक्सिकन गणराज्य, सेंट्रलिस्ट रिपब्लिक ऑफ मैक्सिको, सिएटे लेयेस, और मैक्सिको और स्पेन के बीच शांति और दोस्ती के निश्चित संधि 1824 में मेक्सिको। अल्टा कैलिफ़ोर्निया में आज के नेवादा शामिल रहे। फ़्रांसिस्को गार्सेस इ क्षेत्र में आए वाला पहीले यूरोपीय रहे। [26] नेवादा के न्यू स्पेन के उत्तर-पश्चिमी क्षेत्र में स्पेनिश साम्राज्य के एगो हिस्सा के रूप में शामिल कर लिहल गइल रहे। प्रशासनिक रूप से, नेवादा के क्षेत्र न्यू स्पेन के वायसरायल्टी में प्रोविंसियास इंटर्नास के कमांडेंसी जनरल के हिस्सा रहे। 1804 में जब कैलिफ़ोर्निया के विभाजन भइल त नेवादा अल्टा कैलिफ़ोर्निया (ऊपरी कैलिफ़ोर्निया) प्रांत के हिस्सा बन गइल। 1821 में मैक्सिकन स्वतंत्रता संग्राम के जीत के साथे, अल्टा कैलिफोर्निया प्रांत छोट आबादी के साथे मैक्सिको के एगो क्षेत्र (राज्य) बन गइल। जेडेदिया स्मिथ 1827 में लास वेगास घाटी में प्रवेश कइल, और पीटर स्केन ओग्डेन ने 1828 में हम्बोल्ट नदी के यात्रा काईलस। जब मॉर्मन  1847 में डेसेरेट राज्य बनल, त उ ग्रेट बेसिन औरी कोलोराडो जलक्षेत्र के भीतर पूरा नेवादा पर दावा कइलस। उ 1851 में, जौन अब नेवादा बा, मॉर्मन स्टेशन (आधुनिक जेनोआ) में पहली श्वेत बस्ती के स्थापना कइल गइल। जून 1855 में, विलियम ब्रिंगहर्स्ट औरी यूटा के 29 साथी मॉर्मन मिशनरी लास वेगास शहर के उत्तर-पूर्व में एगो साइट पर पहुंचले औरी निर्माण भइल 150 फुट वर्गाकार एडोब किला, घाटी में निर्मित पहिला स्थायी संरचना, जौन 1858-1859 कि सर्दियों तक साल्ट लेक सिटी के नियंत्रण में रही।{{अमेरिका}} {{यूएस-भू-आधार}} [[श्रेणी:यूनाइटेड स्टेट्स ऑफ अमेरिका के प्रांत]] 9sa3fcw7l6fvzkwpeixji4uumvcz9j5 चाय 0 5572 803366 782522 2026-07-29T16:24:03Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803366 wikitext text/x-wiki [[चित्र:Tea leaves steeping in a zhong čaj 05.jpg|256px|thumb|चाय के पतई पानी में सिझावल जात बा]] '''चाय''' भाषा '''चाह''' ({{Lang|en|tea}}, टी) एगो महकदार पेय (पियल जाये वाला) चीज ह जे [[चाय के पौधा|चाय के पौधवन]] के हरियर चाहे सुखावल [[पतई|पतइन]], जेकरा चायपत्ती कहल जाला, के ऊपर गरम भा खउलत पानी ढरका के; चाहे पानी में अइसन चायपत्ती के खउला तइयार कइल जाला। [[भारतीय उपमहादीप]] में एकर [[दूध]] के साथे पका के बनावल जाये वाला तरीका बेसी चलन में बाटे। चाय के पौधा (कैमेलिया साइनेंसिस) मूल रूप से दक्खिन-पच्छिमी चीन आ उत्तरी म्यांमार के बीचा के बार्डर के आसपास के इलाका के पौधा मानल जाला। चाय भी बनेला, लेकिन बहुत कम, कैमेलिया टैलिएंसिस के पत्ता से। सादा पानी के बाद चाय दुनिया में सभसे ढेर खपत होखे वाला पेय हवे।[[iarchive:empireoftearemar00macf/page/32/mode/2up|[9]]] चाय के कई तरह के होला; कुछ में ठंडा करे वाला, हल्का कड़वा आ कसैला स्वाद होला [https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4462044/ <nowiki>[10]</nowiki>] जबकि कुछ में अइसन प्रोफाइल होला जेह में मीठा, अखरोट वाला, फूलन के भा घास के नोट सामिल होला। चाय के मुख्य रूप से कैफीन के मात्रा के चलते मनुष्य में उत्तेजक प्रभाव होखेला। चाय पीये के सुरुआती बिस्वास जोग रिकार्ड तीसरी सदी ईसवी के हवे, चीनी चिकित्सक हुआ तुओ द्वारा लिखल गइल मेडिकल टेक्स्ट में।[https://books.google.co.in/books?id=YdpL2YCGLVYC&pg=PA63&redir_esc=y#v=onepage&q&f=false <nowiki>[12]</nowiki>] चीनी तांग राजवंश के दौरान एकरा के मनोरंजन खातिर पेय के रूप में लोकप्रिय बनावल गइल आ एकरे बाद चाय पीये के काम पूरबी एशिया के अउरी देस सभ में भी फइल गइल। पुर्तगाली पुजारी आ व्यापारी लोग 16वीं सदी के दौरान एकरा के यूरोप में ले आइल।[https://books.google.co.in/books?id=YdpL2YCGLVYC&pg=PA63&redir_esc=y#v=onepage&q&f=false <nowiki>[13]</nowiki>] 17वीं सदी के दौरान अंग्रेजन के बीच चाय पीये के फैशन हो गइल, जवन कि ब्रिटिश भारत में बड़ पैमाना प चाय के रोपनी शुरू कइले। हर्बल टी शब्द से मतलब होला अइसन पेय जे कैमेलिया साइनेंसिस से ना बनल होखे। ई फल, पत्ता भा [[पौधा]] के अउरी हिस्सा सभ के इंफ्यूजन हवें, जइसे कि [[गुलाब]] के कूल्ह, कैमोमाइल भा रूइबो के खड़ा हिस्सा। एकरा के टिसाने भा जड़ी-बूटी के जलसेक कहल जा सकेला ताकि चाय के पौधा से बनल चाय से भ्रम ना होखे। == व्युत्पत्ति के बारे में बतावल गइल बा == चाय खातिर बिबिध शब्द सभ के व्युत्पत्ति चीन से दुनिया भर के देस सभ में चाय पीये के संस्कृति आ ब्यापार के संचरण के इतिहास के देखावे ले।[https://books.google.co.in/books?id=_TR_PQAACAAJ&redir_esc=y <nowiki>[14]</nowiki>] दुनिया भर में चाय खातिर लगभग सभ शब्द तीन गो बिसाल समूह में आवे लें: ते, चा आ चाय, अंग्रेजी में चाय, चा भा चार, आ चाय के रूप में मौजूद। तीनों में से सभसे सुरुआती अंग्रेजी में प्रवेश करे वाला चा बाटे जे 1590 के दशक में पुर्तगाली लोग के माध्यम से आइल, जे लोग मकाओ में ब्यापार कइल आ एह शब्द के कैंटोनीज़ उच्चारण उठा लिहल। तीसरा रूप चाय (मतलब "मसालादार चाय") के उत्पत्ति चा के उत्तरी चीनी उच्चारण से भइल जे जमीनी रास्ता से मध्य [[एशिया]] आ फारस के यात्रा कइलस जहाँ ई फारसी अंत यी उठा लिहलस। == उत्पत्ति आ इतिहास के बारे में बतावल गइल बा == '''वनस्पति के उत्पत्ति के बारे में बतावल गइल बा''' चाय के [[पौधा]] पूरबी एशिया के मूल निवासी हवें आ चाय के उत्पत्ती के संभावित केंद्र इरावाडी नदी के स्रोत के लगे बा जहाँ से ई पंखा के हिसाब से दक्खिन-पूरबी चीन, भारत-चीन आ असम में फइलल। एह तरीका से चाय के पौधा के प्राकृतिक घर के पच्छिम में बर्मा सीमा के किनारे नागालैंड, मणिपुर आ [[मिज़ोरम|मिजोरम]] के बीच के तुलनात्मक रूप से छोट पंखा के आकार के इलाका के भीतर मानल जाला, चीन से हो के पूरब में चेकियांग प्रांत ले, आ एह लाइन से आमतौर पर दक्खिन ओर पहाड़ी सभ से हो के बर्मा आ थाईलैंड ले वियतनाम ले। ऊपर बतावल गइल पच्छिम-पूरबी अक्ष लगभग 2,400 किमी लंबा बा जे देशांतर 95°-120°E ले फइलल बा। उत्तर-दक्खिन के अक्ष लगभग 1,920 किमी के दूरी पर बाटे, ई बर्मा के उत्तरी हिस्सा से शुरू हो के [[अक्षांश]] 29°N युन्नान, टोंगकिन, थाईलैंड, लाओस से हो के अन्नान ले हो के अन्नान ले पहुँचे ले आ अक्षांश 11°N ले पहुँचे ले।[https://www.etymonline.com/word/tea] [[चीनी]] (छोट पत्ता वाला) प्रकार के चाय (C. sinensis var. sinensis) के उत्पत्ती दक्खिनी चीन में हो सके ला जे संभवतः जंगली चाय के अज्ञात रिश्तेदार सभ के संकरीकरण के साथ भइल होखे। हालाँकि, चूंकि एह चाय के जंगली आबादी के बारे में कौनों जानकारी नइखे, एह से एकर उत्पत्ती अनुमानित बाटे।[https://books.google.co.in/books?id=_TR_PQAACAAJ&redir_esc=y] अलग-अलग क्लेड बनावे वाला इनहन के आनुवांशिक अंतर के देखत, चीनी असम प्रकार के चाय (C. sinensis var. assamica) के दू गो अलग-अलग माता-पिता हो सके ला – एगो दक्खिनी युन्नान (Xishuangbanna, Pu'er City) में पावल जाला आ दुसरा पच्छिमी युन्नान (Lincang, बाओशन) के बा)। कई किसिम के दक्खिनी युन्नान असम चाय के निकट संबंधित प्रजाति कैमेलिया टैलेन्सिस के साथ संकरित कइल गइल बा। दक्खिनी युन्नान असम के चाय के बिपरीत, पच्छिमी युन्नान असम के चाय में भारतीय असम प्रकार के चाय (C. sinensis var. assamica भी) के साथ कई गो आनुवांशिक समानता बा। एह तरीका से पच्छिमी युन्नान असम के चाय आ भारतीय असम के चाय दुनों के उत्पत्ती ओह इलाका में एकही मूल पौधा से भइल होखी जहाँ दक्खिन-पच्छिम चीन, भारत-बर्मा आ तिब्बत मिले ला। हालाँकि, भारतीय असम चाय के पश्चिमी युन्नान असम चाय के साथ कौनों हैप्लोटाइप ना होखे के कारण, भारतीय असम चाय के उत्पत्ति स्वतंत्र पालतू बनावे से भइल होखे के संभावना बा। कुछ भारतीय [[आसाम|असम]] चाय कैमेलिया प्यूबिकोस्टा प्रजाति के साथ संकरित भइल लउके ला। 12 साल के पीढ़ी मान लिहल जाव त चीनी छोट पत्ता वाला चाय लगभग 22,000 साल पहिले असम चाय से अलग होखे के अनुमान लगावल जाला जबकि चीनी असम चाय आ भारतीय असम चाय 2,800 साल पहिले अलग हो गइल रहे। चीनी छोट पत्ता वाला चाय आ असम चाय के विचलन अंतिम हिमनदी अधिकतम के अनुरूप होई।[https://teaorb.com/en-us/blog/origin-of-tea] {{Webarchive|url=https://web.archive.org/web/20230815111058/https://teaorb.com/en-us/blog/origin-of-tea |date=2023-08-15 }} '''जल्दी चाय पीये के बा''' प्राचीन पूर्वी एशिया के लोग सदियन से, शायद सहस्राब्दी तक चाय खात रहे, ओकरा बाद कबो एकरा के पेय पदार्थ के रूप में सेवन कइले रहे। कच्चा पतई के निबड़त रहले, सूप भा साग में डालत रहले, भा सुपारी चबावे के संगे किण्वन क के चबावत रहले। शांग राजवंश के दौरान युन्नान के इलाका में चाय पीये के सुरुआत भइल होखी जहाँ एकर इस्तेमाल औषधीय काम में होखे। इहो मानल जाला कि सिचुआन में "लोग चाय के पत्ता के सेवन खातिर उबाल के बिना अउरी पत्ता भा जड़ी-बूटी के मिला के गाढ़ तरल पदार्थ में बदले लगलें, एह तरीका से चाय के इस्तेमाल औषधीय गढ़ल के रूप में ना बलुक कड़वा फिर भी उत्तेजक पेय के रूप में कइल गइल।" चीनी कथा सभ में चाय के आविष्कार के श्रेय 2737 ईसा पूर्व में पौराणिक शेननोंग (मध्य आ उत्तरी चीन में) के बतावल जाला, हालाँकि, सबूत बतावे लें कि चाय पीये के सुरुआत चीन के दक्खिन-पच्छिम (सिचुआन/युन्नान इलाका) से भइल हो सके ला।[20] चाय के सबसे पहिले के लिखल रिकार्ड चीन से आवेला। tú 荼 शब्द शिजिंग आ अउरी प्राचीन ग्रंथ सभ में एक किसिम के "कड़वा सब्जी" (苦菜) के बोध करावे खातिर आवे ला आ संभव बा कि ई कई अलग-अलग पौधा सभ के जिकिर कइल गइल होखे जइसे कि सोव थिसल, चिकोरी भा स्मार्टवीड,[22] साथ ही चाय भी।[23] हुआयांग के [[इतिहास]] में ई दर्ज बा कि सिचुआन में बा लोग झोउ राजा के तू भेंट कइल।[https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4878758/] बाद में किन लोग बा राज्य आ एकरे पड़ोसी शु के जीत लिहल आ 17वीं सदी के बिद्वान गु यानवु के अनुसार जे री झी लू (日知錄) में लिखले रहलें: "किन लोग शु के ले लिहला के बाद ही चाय पीये के तरीका सीखल।" ।"[2] चाय के एगो अउरी संभावित सुरुआती संदर्भ किन राजवंश के जनरल लियू कुन द्वारा लिखल एगो चिट्ठी में मिले ला जे निहोरा कइलें कि कुछ "असली चाय" उनके भेजल जाय।[https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5788969/] चाय के सभसे पहिले के ज्ञात [[भौतिकी|भौतिक]] सबूत[25] 2016 में ज़ियान के हान के सम्राट जिंग के मकबरा में मिलल, ई बतावे ला कि कैमेलिया जीनस के चाय हान राजघराना के सम्राट लोग दूसरी सदी ईसा पूर्व से ही पीयत रहे। हान राजवंश के रचना, "द कॉन्ट्रैक्ट फॉर ए यूथ", वांग बाओ द्वारा 59 ईसा पूर्व में लिखल गइल [27] में चाय उबाल के पहिला ज्ञात संदर्भ दिहल गइल बा। युवक द्वारा कइल जाए वाला काम सभ में से अनुबंध में कहल गइल बा कि "ऊ चाय उबाल के बर्तन भर दीहें" आ "ऊ वुयांग में चाय खरीदीहें"।[https://web.archive.org/web/20020803115304/http://chcp.org/tea.html <nowiki>[2]</nowiki>] चाय के खेती के पहिला रिकार्ड भी एह समय के बतावल जाला, एह दौरान चेंगदू के लगे मेंग पर्वत (蒙山) पर चाय के खेती भइल। चाय पीये के एगो अउरी सुरुआती बिस्वास जोग रिकार्ड 3वीं सदी ईसवी के हवे, हुआ तुओ के एगो मेडिकल टेक्स्ट में, जेकरा में कहल गइल बा कि "[[कड़ा|कड़वा]] t'u पीये से लगातार बेहतर सोचे के पड़े ला।"[https://books.google.co.in/books?id=AGaTAgAAQBAJ&pg=PA28&redir_esc=y#v=onepage&q&f=false <nowiki>[29]</nowiki>] हालाँकि, तांग राजवंश से पहिले चाय -पीना मुख्य रूप से दक्खिनी चीनी प्रथा रहल जे जियानकांग में केंद्रित रहल।[30] चाय के उत्तरी राजवंश के कुलीन लोग द्वारा तिरस्कार कइल गइल, जे लोग एकरा के दही से नीच बतावे ला।[https://books.google.co.in/books?id=3qmwywEACAAJ&q=slaves%27+drink&pg=PA76&redir_esc=y#v=snippet&q=slaves'%20drink&f=false <nowiki>[31]</nowiki>][https://books.google.co.in/books?id=PASE4LVLzQ0C&q=yogurt&pg=PA22&redir_esc=y#v=snippet&q=yogurt&f=false <nowiki>[32]</nowiki>] तांग राजवंश के दौरान ई बहुत लोकप्रिय भइल, जब ई कोरिया, जापान आ वियतनाम में भी फइलल। चाय आ एकरे तइयारी पर लिखल एगो ग्रंथ द क्लासिक ऑफ टी 8वीं सदी के चीनी लेखक लू यू द्वारा लिखल गइल बा। चीन में चाय पीये पर उनकर बहुत असर पड़ला के बारे में जानल जात रहे। '''विकास के बारे में बतावल गइल बा''' सदियन के दौरान चाय के प्रोसेसिंग खातिर कई तरह के तकनीक, आ चाय के कई गो अलग-अलग रूप के बिकास भइल। तांग राजवंश के दौरान चाय के भाप में पकावल जाला, फिर कुचल के केक के रूप में आकार दिहल जाला [https://books.google.co.in/books?id=_TR_PQAACAAJ&redir_esc=y <nowiki>[34]</nowiki>] जबकि सोंग राजवंश में ढीला पत्ता वाला चाय के बिकास भइल आ लोकप्रिय भइल। युआन आ मिंग राजवंश के दौरान, बिना ऑक्सीडाइज चाय के पत्ता के पहिले गरम सूखा पैन में हिलावल जात रहे, फिर रोल क के हवा में सुखावल जात रहे, ई प्रक्रिया ऑक्सीकरण प्रक्रिया के रोक देले जवना से पत्ता गहिरा हो जाईत, जवना से चाय हरियर रहेला। 15वीं सदी में ऊलोंग चाय के बिकास भइल जेह में कड़ाही में गरम करे से पहिले पतई सभ के आंशिक रूप से ऑक्सीडाइज होखे दिहल जाला। हालाँकि, पश्चिमी स्वाद पूरा तरीका से ऑक्सीडाइज कइल करिया चाय के पक्ष में रहल आ पतई सभ के अउरी ऑक्सीडाइज होखे दिहल गइल। पीला चाय मिंग राजवंश के दौरान हरी चाय के उत्पादन में आकस्मिक खोज रहल, जब प्रतीत होखे वाला लापरवाह प्रथा के कारण पत्ता पीला हो गइल, जेकरा से अलग स्वाद मिलल।[https://books.google.co.in/books?id=_TR_PQAACAAJ&redir_esc=y <nowiki>[35]</nowiki>] '''दुनिया भर में फइल गइल''' चाय के पहिली बेर चीन में पच्छिमी पुजारी आ व्यापारी लोग के 16वीं सदी के दौरान पेश कइल गइल आ ओह समय एकरा के चा कहल गइल।[13] चाय के सभसे सुरुआती यूरोपीय संदर्भ, जेकरा के चियाई के नाँव से लिखल जाला, वेनिस के जियामबतिस्ता रामुसिओ द्वारा 1545 में लिखल डेले नेविगेशनी ई वियाजी से मिलल।[https://books.google.co.in/books?id=_TR_PQAACAAJ&redir_esc=y <nowiki>[36]</nowiki>] यूरोपीय राष्ट्र द्वारा चाय के पहिला रिकार्ड खेप 1607 में भइल जब डच ईस्ट इंडिया कंपनी मकाओ से चाय के एगो माल जावा ले गइल, फिर दू साल बाद, डच लोग चाय के पहिला असाइनमेंट खरीदल जवन जापान के हिराडो से रहे यूरोप में भेजल गइल।[https://books.google.co.in/books?id=_TR_PQAACAAJ&redir_esc=y <nowiki>[37]</nowiki>] नीदरलैंड के हेग में चाय एगो फैशनेबल पेय बन गइल आ डच लोग एह पेय के जर्मनी, फ्रांस आ अटलांटिक के ओह पार न्यू एम्स्टर्डम (न्यूयॉर्क) में पेश कइल।[https://books.google.co.in/books?id=_TR_PQAACAAJ&redir_esc=y <nowiki>[38]</nowiki>] 1567 में रूसी लोग चाय के संपर्क में आइल जब कोसाक अटामन पेट्रोव आ यालिशेव चीन के दौरा कइलें।[https://www.apollotea.com/tea-articles/tea-history/15-russian-tea-history <nowiki>[39]</nowiki>] मंगोलियाई खान 1638 में ज़ार माइकल प्रथम के चार पूड (65–70 किलोग्राम) चाय दान कइलें। जेरेमिया कर्टिन के अनुसार, संभवतः 1636 में वासिली स्टारकोव के अल्टिन खान के दूत के रूप में भेजल गइल रहे। उनुका के 250 पाउंड के चाय ज़ार के उपहार में दिहल गइल। स्टारकोव पहिले त मना कर दिहलन, मरल पतई के बोझ के कवनो फायदा ना देखत, बाकिर खान जिद कइलन. एह तरह से रूस में चाय के परिचय भइल. 1679 में रूस फर के बदले चीन से ऊंट के कारवां के माध्यम से नियमित चाय के आपूर्ति के संधि कइलस। आज एकरा के वास्तविक रूप से राष्ट्रीय पेय मानल जाला। अंग्रेजी में चाय के पहिला रिकार्ड रिचर्ड विकहम के लिखल चिट्ठी से आइल जे जापान में ईस्ट इंडिया कंपनी के एगो ऑफिस चलावत रहलें, मकाओ के एगो व्यापारी के लिख के 1615 में "बेस्ट सॉर्ट ऑफ चाव" के निहोरा कइले रहलें।पीटर मुंडी, एगो यात्री आ व्यापारी रहलें जे 1637 में फुजियान में चाय के सामना कइले रहलें, लिखलें कि "चा – खाली पानी जेह में एक तरह के जड़ी-बूटी के बॉयल कइल होखे"।[https://books.google.co.in/books?id=lXYFBQAAQBAJ&pg=PT25&redir_esc=y#v=onepage&q&f=false <nowiki>[43][44]</nowiki>] 1657 में लंदन के एगो कॉफी हाउस में चाय बिकाइल, सैमुअल पेपिस 1660 में चाय के स्वाद लिहलें आ ब्रागांजा के कैथरीन 1662 में चार्ल्स द्वितीय से बियाह कइला पर चाय पीये के आदत के अंग्रेजी दरबार में ले गइली, हालाँकि, चाय के इस्तेमाल बहुत ढेर ना भइल ब्रिटिश दीप सभ में 18वीं सदी ले आ ओह समय के अंतिम हिस्सा ले महंगा रहल। अंग्रेजी पीये वाला लोग काली चाय में चीनी आ दूध डालल पसंद कइल आ 1720 के दशक में काली चाय ग्रीन टी के लोकप्रियता में पाछा छोड़ दिहलस।[https://www.bbc.co.uk/programmes/p004y24y <nowiki>[45]</nowiki>] 18वीं सदी के दौरान चाय के तस्करी के चलते आम जनता चाय के खरीददारी अउरी चाय के सेवन करे में सक्षम हो गइल। ब्रिटिश सरकार चाय पर टैक्स हटा दिहलस, जेकरा से तस्करी के ब्यापार खतम हो गइल, 1785 ले।[https://web.archive.org/web/20090730000451/http://www.tea.co.uk/page.php?id=98 <nowiki>[46]</nowiki>] ब्रिटेन आ आयरलैंड में शुरू में चाय के सेवन खास मौका पर, जइसे कि धार्मिक परब, जागरण आ घरेलू काम के जमघट पर लग्जरी आइटम के रूप में कइल जात रहे। यूरोप में चाय के दाम 19वीं सदी के दौरान लगातार गिरल, खासतौर पर भारतीय चाय के भारी मात्रा में आवे लागला के बाद; 19वीं सदी के अंत ले चाय [[समाज]] के हर स्तर खातिर रोजमर्रा के पेय पदार्थ बन गइल रहे।[https://web.archive.org/web/20090730000451/http://www.tea.co.uk/page.php?id=98 <nowiki>[47]</nowiki>] चाय के लोकप्रियता के ऐतिहासिक घटना में भूमिका रहे – 1773 के चाय अधिनियम बोस्टन चाय पार्टी के भड़का दिहलस जवन अमेरिकी क्रांति में बढ़ल। चाय के व्यापार के वजह से ब्रिटिश व्यापार घाटा के मुद्दा के संबोधित करे के जरूरत के परिणामस्वरूप अफीम युद्ध शुरू भईल| किंग कांगसी सम्राट चीन में बिदेसी उत्पाद सभ के बेचे पर रोक लगा दिहले रहलें आ 1685 में फरमान जारी कइलें कि चीन से खरीदल सगरी सामान सभ के भुगतान चांदी के सिक्का भा बुलियन में होखे के चाहीं।[https://books.google.co.in/books?id=mOu_DQAAQBAJ&q=chinese+european+bullion&pg=PT365&redir_esc=y#v=snippet&q=chinese%20european%20bullion&f=false <nowiki>[48]</nowiki>] एकरा बाद दोसरा राष्ट्रन के व्यापारी एगो अउरी उत्पाद खोजे के कोशिश कइले, एह मामला में अफीम, जवना के चीन के बेचे के कोशिश कइल कि ऊ चाँदी वापस कमा सके जवना के चाय आ अउरी सामानन के पइसा देबे के पड़ी. एकरे बाद चीन सरकार द्वारा अफीम के ब्यापार में कटौती करे के कोसिस सभ के कारण लड़ाई के सुरुआत भइल।[https://books.google.co.in/books?id=YIyV_5wrplMC&pg=PA26&redir_esc=y <nowiki>[49]</nowiki>] चीनी छोट पत्ता वाला किसिम के चाय के भारत में 1836 में अंग्रेज लोग द्वारा चाय पर चीनी एकाधिकार के तोड़े के कोसिस में ले आवल गइल।[https://books.google.co.in/books?id=YIyV_5wrplMC&pg=PA26&redir_esc=y <nowiki>[50]</nowiki>] 1841 में आर्चीबाल्ड कैम्पबेल कुमाऊन क्षेत्र से चीनी चाय के बीया ले अइलें आ दार्जिलिंग में चाय रोपनी के प्रयोग कइलें। अलुबारी चाय के बगीचा 1856 में खुलल आ दार्जिलिंग चाय के उत्पादन शुरू भइल।[https://books.google.co.in/books?id=_TR_PQAACAAJ&redir_esc=y <nowiki>[51]</nowiki>] 1848 में रॉबर्ट फॉर्च्यून के ऑनरेबल ईस्ट इंडिया कंपनी चीन के मिशन पर भेजले रहे ताकि चाय के संयंत्र के वापस ग्रेट ब्रिटेन ले आवल जा सके। ऊ आपन सफर बहुत गोपनीयता में शुरू कइलें काहें से कि उनकर मिशन पहिला अफीम युद्ध आ दुसरा अफीम युद्ध के बीच के सुस्ती में भइल।[https://books.google.co.in/books?id=_TR_PQAACAAJ&redir_esc=y &#91;52&#93;] ऊ जवन चीनी चाय के पौधा वापस ले अइले, ओकरा के हिमालय में ले आवल गइल, हालांकि अधिकतर ना बचल। अंग्रेजन के पता चलल रहे कि असम आ भारत के पूर्वोत्तर क्षेत्र में चाय के एगो अलग किसिम के स्थानिक बा जवना के बाद चीनी छोट पत्ता वाला प्रकार के चाय के संगे संकरित कइल गइल।<ref>{{Cite web |date=2015-02-23 |title=TED Case Study: Ceylon Tea |url=http://www1.american.edu/ted/ceylon-tea.htm |access-date=2023-10-04 |website=web.archive.org |archive-date=2015-02-23 |archive-url=https://web.archive.org/web/20150223084443/http://www1.american.edu/ted/ceylon-tea.htm |url-status=dead }}</ref> चीनी रोपनी आ खेती के तकनीक के इस्तेमाल से ब्रिटिश औपनिवेशिक सरकार असम में जमीन के पेशकश क के चाय उद्योग के स्थापना कइलस जे निर्यात खातिर खेती करे के सहमत होखे।[50] चाय के सेवन मूल रूप से खाली एंग्लो-इंडियन लोग करत रहे; हालाँकि, इंडिया टी बोर्ड के सफल बिज्ञापन अभियान के कारण 1950 के दशक में भारत में ई बहुत लोकप्रिय भइल।[50] अंग्रेज लोग 1867 में श्रीलंका (तब के सीलोन) में चाय उद्योग शुरू कइल।[https://web.archive.org/web/20150223084443/http://www1.american.edu/ted/ceylon-tea.htm <nowiki>[53]</nowiki>] == रासायनिक संरचना के बारे में बतावल गइल बा == शारीरिक रूप से देखल जाए त चाय में घोल अउरी निलंबन दुनो के गुण होखेला। ई चाय के पत्ता से निकालल गइल सगरी पानी में घुलनशील यौगिक सभ के घोल हवे, जइसे कि पॉलीफेनोल आ अमीनो एसिड, बाकी जब सभ अघुलनशील घटक सभ के बिचार कइल जाय तब ई निलंबन हवे, जइसे कि चाय के पत्ता में सेल्यूलोज। [उद्धरण के जरूरत बा] चाय के जलसेक वैश्विक स्तर पर सभसे ढेर खपत होखे वाला पेय पदार्थ सभ में गिनल जाला।[54] कैफीन चाय के सूखा वजन के लगभग 3% हिस्सा होला, जेकर अनुवाद प्रकार, ब्रांड,[55] आ ब्रूइंग के तरीका के आधार पर प्रति 250-मिलीलीटर (8 1⁄2 US fl oz) कप में 30 से 90 मिलीग्राम के बीच होला।[56] एगो अध्ययन में पावल गइल कि एक ग्राम काली चाय में कैफीन के मात्रा 22 से 28 मिलीग्राम के बीच रहे जबकि एक ग्राम ग्रीन टी में कैफीन के मात्रा 11 से 20 मिलीग्राम के बीच रहे, जवन कि एगो महत्वपूर्ण अंतर के देखावेला।[57] चाय में थियोब्रोमाइन आ थियोफिलिन के भी कम मात्रा होला जे कैफीन नियर ज़ैन्थिन आ उत्तेजक होलें।[58] चाय में कसैलापन के कारण पॉलीफेनोल के मौजूदगी बतावल जा सकता। ई चाय के पत्ता में सभसे ढेर पावल जाए वाला यौगिक हवें, इनहन के रचना के 30–40% हिस्सा होलें।[59] चाय में मौजूद पॉलीफेनोल सभ में फ्लेवोनोइड, एपिगैलोकैटेचिन गैलेट (EGCG), आ अउरी कैटेचिन सभ के सामिल कइल जाला।[60][61] हालाँकि, एह बात पर सुरुआती नैदानिक ​​रिसर्च भइल बा कि हरियर भा करिया चाय से मनुष्य के बिबिध बेमारी सभ से बचाव हो सके ला, अइसन कौनों सबूत नइखे मिलल कि चाय के पॉलीफेनोल सभ के स्वास्थ्य पर कौनों परभाव पड़े ला या बेमारी के जोखिम कम करे ला।[62][63] == स्वास्थ्य पर असर पड़ेला == हालाँकि, कैमेलिया साइनेंसिस के सेवन के पूरा इतिहास में स्वास्थ्य के फायदा मानल गइल बा, अइसन कौनों उच्च गुणवत्ता वाला सबूत नइखे मिलल जे ई बतावे कि चाय के सेवन से संभवतः सतर्कता बढ़ावे के अलावा अउरी कौनों खास फायदा मिले ला, ई परभाव चाय के पत्ता में कैफीन के कारण होला।[64][65] 21वीं सदी के सुरुआत में भइल क्लिनिकल रिसर्च में ई पावल गइल कि अइसन कौनों वैज्ञानिक सबूत नइखे मिलल जेह में ई बतावल जा सके कि चाय के सेवन से कौनों भी बेमारी पर परभाव पड़े ला या स्वास्थ्य में सुधार होला।[64] काली आ हरियर चाय में कौनों जरूरी पोषक तत्व महत्वपूर्ण मात्रा में ना होला, आहार खनिज मैंगनीज के छोड़ के, 0.5 मिलीग्राम प्रति कप या संदर्भ दैनिक सेवन (RDI) के 26% होला।[66] चाय में कबो-कबो फ्लोराइड भी मौजूद होला; कुछ खास किसिम के "ईंट के चाय", पुरान पत्ता आ तना से बनल, सभसे ढेर मात्रा में होला, ई काफी होला कि अगर ढेर चाय पीयल जाय तब स्वास्थ्य के खतरा पैदा हो सके ला, एकर कारण माटी, अम्लीय माटी आ लंबा समय ले ब्रूइंग में फ्लोराइड के ढेर मात्रा बतावल गइल बा .[67] == खेती आ कटाई के काम होला == कैमेलिया साइनेंसिस एगो सदाबहार पौधा हवे जे मुख्य रूप से उष्णकटिबंधीय आ उपोष्णकटिबंधीय जलवायु में बढ़े ला।[68] कुछ किसिम सभ समुंद्री जलवायु के भी सहन क सके लीं आ इंग्लैंड के कॉर्नवाल,[69] स्कॉटलैंड के पर्थशायर,[70] अमेरिका के वाशिंगटन,[71] आ कनाडा के वैंकूवर दीप ले उत्तर में खेती कइल जाला।[72] दक्खिनी गोलार्ध में चाय के खेती तस्मानिया के होबार्ट[73][74] आ न्यूजीलैंड के वाइकाटो ले दक्खिन में होला।[75] चाय के पौधा सभ के प्रजनन बीया आ कलम से होला; पौधा के बीया पैदा करे खातिर लगभग 4 से 12 साल के जरूरत होला आ नया पौधा के कटाई खातिर तइयार होखे से लगभग तीन साल के जरूरत होला।[68] जोन 8 के जलवायु भा एकरे से ढेर गरम जलवायु के अलावा, चाय के पौधा सभ में हर साल कम से कम 127 सेमी (50 इंच) बरखा के जरूरत होला आ अम्लीय माटी पसंद करे लें।[76] कई गो उच्च गुणवत्ता वाला चाय के पौधा सभ के खेती समुंद्र तल से 1,500 मीटर (4,900 फीट) ले ऊँचाई पर होला। हालाँकि, एह ऊँचाई सभ पर पौधा सभ के बढ़ती धीरे-धीरे होला, इनहन के स्वाद बेहतर मिले ला।[77] दू गो प्रमुख किसिम के इस्तेमाल होला: कैमेलिया साइनेंसिस वर्. sinensis, जेकर इस्तेमाल अधिकतर चीनी, फॉर्मोसन आ जापानी चाय सभ खातिर होला आ सी. sinensis var. assamica, पु-एर्ह आ अधिकतर भारतीय चाय (लेकिन दार्जिलिंग ना) में इस्तेमाल होला। एह वनस्पति किसिम सभ के भीतर कई गो उपजाति आ आधुनिक क्लोनल किसिम सभ के जानकारी बा। चाय के पौधा सभ के वर्गीकरण खातिर पत्ता के आकार मुख्य पैमाना हवे, तीन गो प्राथमिक वर्गीकरण बाड़ें:[78] असम प्रकार, जेकर बिसेसता सभसे बड़हन पत्ता होखे लीं; चीन प्रकार, एकर बिसेसता सभसे छोट पतई होला; आ कंबोडियन प्रकार के, एकर बिसेसता बा कि पत्ता मध्यवर्ती आकार के होलें। कंबोडियन प्रकार के चाय (C. assamica subsp. lasiocaly) के मूल रूप से असम के चाय के एगो प्रकार मानल जात रहे। हालाँकि, बाद के आनुवांशिक काम से पता चलल कि ई चीनी छोट पत्ता वाला चाय आ असम टाइप के चाय के बीच के संकर हवे।[79] दार्जिलिंग के चाय भी चीनी छोट पत्ता वाली चाय आ असम प्रकार के बड़ पत्ता वाली चाय के बीच के संकर के रूप में लउके ले।[80] चाय के पौधा के अगर बिना बिघटन के छोड़ दिहल जाय तब 16 मीटर (52 फीट) तक ले के पेड़ में बढ़ जाई [68] बाकी आमतौर पर खेती कइल पौधा सभ के कमर के ऊँचाई ले छंटाई कइल जाला ताकि उखाड़ में आसानी होखे। साथ ही, छोट पौधा सभ में नया अंकुर ढेर होला जे नया आ कोमल पत्ता देला आ चाय के गुणवत्ता बढ़ावे ला।[81] परिपक्व पौधा के खाली ऊपरी 2.5-5 सेंटीमीटर (1-2 इंच) के चुनल जाला। एह कली आ पतई सभ के 'फ्लश' कहल जाला।[82] एगो पौधा बढ़े के मौसम में हर 7 से 15 दिन में एगो नया फ्लश उगाई। जवन पत्ता सभ के बिकास धीमा होखे, बेहतर स्वाद वाला चाय पैदा करे के परभाव होला।[68] कई गो चाय निर्दिष्ट फ्लश से उपलब्ध बाड़ी सऽ; उदाहरण खातिर, दार्जिलिंग के चाय फर्स्ट फ्लश (प्रीमियम दाम पर), सेकेंड फ्लश, मानसून आ शरद ऋतु के रूप में उपलब्ध बा। असम के सेकेंड फ्लश भा "टिप्पी" चाय के फर्स्ट फ्लश से बेहतर मानल जाला, काहें से कि पतई पर सोना के नोक लउके ला। चाय के पौधा सभ के परेशान करे वाला कीड़ा सभ में मच्छर के कीड़ा, हेलोपेल्टिस जीनस सामिल बाड़ें, ई सच्चा कीड़ा हवें आ कुलिसिडी ('मच्छर') परिवार के डिप्टरस कीड़ा सभ से भ्रमित ना होखे के चाहीं। मच्छर के कीड़ा पौधा के सामग्री के चूसे से, आ पौधा के भीतर अंडा देवे (ओविपोजिशन) दुनों से पतई के नुकसान पहुँचा सके ला। सिंथेटिक कीटनाशक के छिड़काव उचित मानल जा सके ला।[83] अउरी कीड़ा सभ में लेपिडोप्टेरन पत्ता फीडर आ चाय के बिबिध बेमारी सभ के नाँव गिनावल जाला। == Production == == इतिहास == सबसे पहिले सन् 1815 में कुछ अंग्रेज़ यात्रि सब के ध्यान असम में उगे वाली चाय की झाड़ियन पर गइल जेसे स्थानीय क़बाइली लोग एक प्रकार के पेय बनाके पीयत रहे लोग। [[भारत]] के गवर्नर जनरल ''लॉर्ड बैंटिक'' 1834में चाय के परंपरा भारत में शुरू करे आ उ के उत्पादन करे के संभावना तलाश करे खातिर एगो समिति के गठन कइलन। इ के बाद 1835 में [[आसाम|असम]] में चाय के बाग़ लगावल गइल ।<ref>{{cite news|publisher=BBC News|title=चाय के खेती कब, कहाँ आ कइसे?| url=http://www.bbc.co.uk/hindi/news/story/2006/04/060430_ask_us_tea.shtml|date=30 अप्रैल 2006}}</ref> {{clear}} ==संदर्भ== {{reflist}} {{पेय-आधार}} [[श्रेणी:पेय]] fb0ju0kwptdr19d1tj8i53rf36j7mi3 सांकेतिक भाषा 0 9105 803296 724489 2026-07-29T16:06:36Z SM7Bot 7808 /* top */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803296 wikitext text/x-wiki [[चित्र:Sign Language.svg|thumbnail]] [[File:Preservation of the Sign Language (1913).webm|thumb|thumbtime=5|''सांकेतिक भाषा के संरक्षण (१९१३)'']] '''संकेत भाषा आ सांकेतिक भाषा''' एगो अइसन भाषा हऽ, जोन अर्थ सूचित करे खातिर श्रवणीय ध्वनि परकार में संप्रेषित करे के बजाय, दृश्य रूप में सांकेतिक परकार (हस्तचालित संप्रेषण, अंग-संकेत) संचारित करेला। जेकरा में वक्ता के विचार के धाराप्रवाह रूप से व्यक्त करे खातिर, हाथ के आकार, विन्यास अउर संचालन, हाथ आ देह औरी चेहरा के हाव-भाव के एक साथ उपयोग कइल जाला। [[श्रेणी:भाषा]] {{Lang-stub}} e0ahrpqx31fkc7v6hv679jf9gkmbeit नरेंद्र मोदी 0 9279 803347 785088 2026-07-29T16:22:30Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803347 wikitext text/x-wiki {{Infobox officeholder |name = नरेंद्र मोदी |office = [[भारत के प्रधानमंत्री|भारत के 14<sup>वाँँ</sup> परधानमंत्री]] |image = Prime Minister Of Bharat Shri Narendra Damodardas Modi.jpg |alt = |caption = प्रधानमंत्री नरेंद्र मोदी |predecessor = [[मनमोहन सिंह]] |president = [[द्रौपदी मुर्मू]] |succeeding = [[मनमोहन सिंह]] |successor = |signature = Signature of Narendra Modi (Hindi).svg |party = [[भारतीय जनता दल]] |governor1 = सुन्दरसिंह भण्डारी<br>कैलाशपति मिश्र<br>बलराम जाखड़<br>नवलकिशोर शर्मा<br>एस॰ सी॰ जमीर<br>कमला बेनीवाल |office1 = गुजरात के 14वाँँ मुख्यमंत्री |predecessor1 = केशूभाई पटेल |successor1 = आनंदीबेन पटेल |birthname = नरेन्द्र दामोदरदास मोदी |birth_date = {{birth date and age|1950|9|17}} |birth_place = [[वड़नगर]], [[गुजरात]], [[भारत]] |death_date = |death_place = |spouse = जसोदाबेन चिमनलाल<ref>http://erms.gujarat.gov.in/ceo-gujarat/FileLoc/LS2014/20/Narendra_Modi019006.PDF</ref> |alma_mater = [[दिल्ली विश्वविद्यालय]]<br>[[गुजरात विश्वविद्यालय]] | religion = हिन्दू |website = [http://www.narendramodi.in निजी वेबसाइट] |term_start = 26 मई 2014 |term_end = |term_start1 = 7 अक्टूबर 2001 |term_end1 = 22 मई 2014 }}बाड़ें। मोदी 26 मई 2014 के प्रधानमंत्री पद के पहिली बेर शपथ लिहलें; एकरा बाद से ऊ अबतक ले प्रधानमंत्री बाड़ें, अपना तिसरा कार्यकाल क शपथ 9 जून 2024 के लिहलें। भारत के अबतक भइल प्रधानमंत्री लोग में मोदी आजाद भारत में जनमल पहिला प्रधानमंत्री बाड़ें। एकरा पहिले ऊ [[गुजरात]] के मुख्यमंत्री रहलें आ वर्तमान में [[बनारस]] लोकसभा सीट से सांसद बाङे। '''नरेंद्र मोदी''' [[भारतीय जनता दल]] के सदस्य, [[हिंदुत्व|हिंदू राष्ट्रवादी]] आ दक्खिनपंथी संगठन राष्ट्रीय स्वयंसेवक संघ के सदस्य हवें। बडनगर में एगो गुजराती परिवार में पैदा मोदी, बचपन में अपना बाबूजी के चाय के दुकान पर मदद करें, बाद में आपन दुकान लगावे लगलें। स्वयंसेवक संघ से उनके परिचय आ जुड़ाव आठ बरिस के उमिर में भइल। ग्रेजुएशन के पढ़ाई के बाद मोदी घर छोड़ दिहलें, कारण बियाह बतावल जाला जेकरा के मोदी स्वीकार ना कइलें। दू साल खातिर भारत के बिबिध इलाका सभ में घूमे के बाद 1969 या 1970 में अहमदाबाद लवटलें। 1971 में आरएसएस के फुल-टाइम मेंबर बन गइलें आ 1975 के इमरजेंसी के दौर में कुछ दिन इनके लुकाये के परल। 1985 में आरएसएस इनके भाजपा में भेज दिहलस, पार्टी के बिबिध पद पर रह के काम करत साल 2001 में एकर जनरल सेक्रेटरी बनलें। 2001 में नरेंद्र मोदी के गुजरात के मुख्यमंत्री नियुक्त कइल गइल जवना के कारण तत्काल के मुख्यमंत्री केशूभाई पटेल के खराब सेहत आ भुज के भूडोल के बाद उनके छवि में गिरावट रहल। जल्दिये मोदी बिधायक के रूप में चुन लिहल गइलें। मोदी के प्रशासन के 2002 के गुजरात दंगा में भूमिका होखे के बात कहल जाला,{{efn|2002 के गुजरात दंगा में तत्कालीन प्रशासन के भूमिका खातिर स्रोत संदर्भ<ref name="Buncombe" />}} या फिर दंगा करावे के आरोप भी लगावल जाला, हालाँकि कोर्ट के कौनों अइसन सबूत ना मिलल जेकरा आधार पर एक मामिला में मोदी के कौनों सजा दिहल जा सके।{{efn|2012 में न्यायालय कहलस कि तहकीकात में मोदी के खिलाफ कवनो सबूत ना मिलल।<ref>{{cite web|url=http://www.bbc.com/news/world-asia-india-17664751|title=India Gujarat Chief Minister Modi cleared in riots case|publisher=BBC|date=10 April 2012|accessdate=17 February 2017}}</ref><ref>{{cite web|url=http://www.thehindu.com/news/national/sit-finds-no-proof-against-modi-says-court/article3300175.ece|title=SIT finds no proof against Modi, says court|publisher=दि हिंदू|date=10 April 2012|accessdate=17 February 2017}}</ref>}} मुख्यमंत्री के रूप में गुजरात में आर्थिक बढ़ती के आगे रखे में उनके नीति के तारीफ कइल जाला,<ref>{{Cite news|url=http://www.nytimes.com/2012/02/16/world/asia/16iht-letter16.html|title=Shaking Off the Horror of the Past in India|last=Joseph|first=Manu|date=15 February 2012|work=The New York Times|access-date=19 May 2017|issn=0362-4331}}</ref> जबकि सेहत-सुबिधा, आ शिक्षा के बिकास के आगे बढ़ावे के मामिला में आ गरीबी दूर करे में बिफल रहे खातिर उनके सरकार के आलोचना होले।{{efn|मानवीय बिकास में बिफल रहे के बात कहे वाला स्रोत-संदर्भ।<ref name="Buncombe" />}} मोदी 2014 के आम चुनाव में भाजपा के अगुआई कइलेन आ पार्टी लोकसभा में बहुमत से आइल। कौनों पार्टी के 1984 के बाद से पूर्ण बहुमत पहिली बेर मिलल बा। मोदी खुद बनारस लोक सभा सीट से चुनल गइलें। परधानमंत्री बनले के बाद से मोदी प्रशासन के कोसिस रहल बा कि देस में बिदेसी डाइरेक्ट निवेश के बढ़ावा दिहल जाय, मूलभूत संरचना पर खरच ढेर कइल जाय, आ स्वास्थ्य आ सामाजिक कल्याण के ऊपर होखे वाला खर्चा में कटौती कइल जाय। मोदी के सारकार द्वारा ब्यूरोक्रेसी के दक्षता बढ़ावे आ आ सेंट्रलाइज हो चुकल पावर के छितरावे के कोसिस में योजना आयोग के भंग क दिहल गइल बा। इनके द्वारा देस भर में हाई-प्रोफाइल सफाई अभियान चलावल गइल बा आ पर्यावरण आ मजूरी संबंधी कानून के कमजोर भा खतम क दिहल गइल बा। मोदी के देस के राजीनीत के दक्खिनपंथी राजनीति के तरफ ओरमावे खातिर इंजीनियरी करे के क्रेडिट दिहल जाला आ इनके हिंदू राष्ट्रवादी बिसवास आ गुजरात दंगा में भूमिका के ले के देस में आ देस के बाहर बिबादास्पद छवी बा; एह दुनों बात के, सामाजिक भेदभाव वाला एजेंडा रखे वाला ब्यक्ति होखे के सबूत के तौर पर भी प्रस्तुत कइल जाला।{{efn|बिबादास्पद भूमिका के चर्चा संबंधी स्रोत-संदर्भ<ref name="Buncombe">{{cite news |title=A rebirth dogged by controversy |first=Andrew|last=Buncombe |url=http://www.independent.co.uk/news/world/asia/a-rebirth-dogged-by-controversy-2357157.html |work=The Independent |date=19 September 2011 |accessdate=10 अक्टूबर 2012 |location=London}}</ref><ref>{{cite book |last=Menon |first=Kalyani Devaki|title=Everyday Nationalism: Women of the Hindu Right in India |year=2012 |publisher=The University of Pennsylvania Press |isbn=978-0-8122-2234-0 |page=26 |quote=Yet, months after this violent pogrom against Muslims, the Hindu nationalist chief minister of Gujarat, Narendra Modi, went to the polls and won a resounding victory}}</ref><ref>{{cite book|last=Mishra |first=Pankaj |title=Perspectives on Modern South Asia: A Reader in Culture, History, and Representation|date=अप्रैल 2011|publisher=Wiley-Blackwell |isbn=978-1-4051-0062-5|page=188|editor-first=Kamala |editor-last=Visweswaran|quote=The chief minister of Gujarat, a young up-and-coming leader of the Hindu nationalists called Narendra Modi, quoted Isaac Newton to explain the killings of Muslims. "Every action", he said, "has an equal and opposite reaction."}}</ref><ref name="CBC">{{cite news|title=Indian PM Narendra Modi still mired in controversy, says expert|publisher=कनाडियन ब्रॉडकास्टिंग कॉर्पोरेशन|date=16 April 2015|accessdate=17 February 2017|url=http://www.cbc.ca/news/canada/british-columbia/indian-pm-narendra-modi-still-mired-in-controversy-says-expert-1.3036836}}</ref><ref>{{cite news |title=Edgy Indian state election going down to the wire |first=Rupam Jain|last=Nair |url=https://www.reuters.com/article/2007/12/12/us-india-state-poll-idUSDEL17441120071212 |agency=[[Reuters]] |date=12 December 2007 |accessdate=10 अक्टूबर 2012}}</ref><ref name="Robinson">{{cite news |title=India's Voters Torn Over Politician |first=Simon |last=Robinson |url=http://content.time.com/time/world/article/0,8599,1693370,00.html |journal=[[टाइम पत्रिका]] |date=11 December 2007 |accessdate=10 अक्टूबर 2012 |archive-date=23 October 2013 |archive-url=https://web.archive.org/web/20131023030008/http://content.time.com/time/world/article/0,8599,1693370,00.html |url-status=dead }}</ref><ref name="Burke">{{cite news |title=Gujarat leader Narendra Modi grilled for 10&nbsp;hours at massacre inquiry |first1=Jason|last1=Burke |authorlink=:en:Jason Burke |url=https://www.theguardian.com/world/2010/mar/28/gujarat-narendra-modi-massacre-inquiry-india |newspaper=[[दि गार्जियन]] |date=28 मार्च 2010 |accessdate=10 अक्टूबर 2012}}</ref>}} एकरा बाद वर्ष 2019 में भारतीय जनता दल  उनका अगुवाई में दोबारा चुनाव लड़ल औरी एह बेरी पहीले से भी ज्यादा बड़का जीत हासिल भइल। पार्टी  कुल 303 सीट पर जीत हासिल कइलस। भाजपा के समर्थक दल यानी राजग के कुल 352 सीट प्राप्त भइल।<ref>{{Cite web |date=2020-11-11 |title=Alliance Wise Election Live Results 2019: Lok Sabha Elections Result Live Alliance Wise, Party Wise |url=https://www.news18.com/lok-sabha-elections-2019/alliance-wise-tally-live-results/ |url-status=dead |archive-url=https://web.archive.org/web/20201111232305/https://www.news18.com/lok-sabha-elections-2019/alliance-wise-tally-live-results/ |archive-date=2020-11-11 |access-date=2024-12-11 |website=web.archive.org}}</ref> 30 मई 2019 के शपथ ग्रहण क के नरेन्द्र मोदी लगातार दूसरी बार प्रधानमन्त्री बनले।<ref>{{Cite web |date=2020-12-18 |title=PM Narendra Modi Becomes Longest Serving Non-Congress Prime Minister |url=https://www.ndtv.com/people/pm-narendra-modi-becomes-longest-serving-non-congress-prime-minister-2279112 |url-status=dead |archive-url=https://web.archive.org/web/20201218122430/https://www.ndtv.com/people/pm-narendra-modi-becomes-longest-serving-non-congress-prime-minister-2279112 |archive-date=2020-12-18 |access-date=2024-12-12 |website=web.archive.org}}</ref> 2019 के आम चुनाव में उनकर पार्टी के जीत के बाद, उनके प्रशासन  जम्मू औरी कश्मीर के विशेष राज्य के दर्जा के रद्द कर दिहाल। उनके प्रशासन  नागरिकता (संशोधन) अधिनियम, २०१९ भी पेश कइल, जिके परिणामस्वरूप देश भर में व्यापक विरोध प्रदर्शन भइल। मोदी अपना हिन्दू राष्ट्रवादी विश्वास औरी 2002 के गुजरात दंगा के दौरान उनकर कथित भूमिका पर्वघरेलू औरी अन्तरराष्ट्रीय स्तर पर विवाद के एगो आँकड़ा बनल बाटे,<ref>{{Cite journal |last=Jaffrelot |first=Christophe |date=2013-06-01 |title=Gujarat Elections: The Sub-Text of Modi’s ‘Hattrick’—High Tech Populism and the ‘Neo-middle Class’ |url=https://journals.sagepub.com/doi/10.1177/2321023013482789 |journal=Studies in Indian Politics |language=en |volume=1 |issue=1 |pages=79–95 |doi=10.1177/2321023013482789 |issn=2321-0230}}</ref> जेके एगो बहिष्कारवादी सामाजिक एजेण्डे के प्रमाण के रूप में उद्धृत कइल गइल बा। मोदी के कार्यकाल में, भारत  लोकतान्त्रिक बैकस्लेडिंग के अनुभव कइले बा।<ref>{{Cite web |date=2021-08-03 |title=Modi's India {{!}} Princeton University Press |url=https://press.princeton.edu/books/hardcover/9780691206806/modis-india |access-date=2024-12-14 |website=press.princeton.edu |language=en}}</ref><ref>{{Cite web |date=2021-02-27 |title=Democratic Backsliding in India, the World’s Largest Democracy {{!}} V-Dem |url=https://www.v-dem.net/en/news/democratic-backsliding-india-worlds-largest-democracy/ |access-date=2024-12-14 |website=web.archive.org |archive-date=2021-02-27 |archive-url=https://web.archive.org/web/20210227182459/https://www.v-dem.net/en/news/democratic-backsliding-india-worlds-largest-democracy/ |url-status=dead }}</ref> ‌‌एकरा बाद वर्ष 2024 में भारतीय जनता दल  उनका नेतृत्त्व में दोबारा चुनाव लड़ल औरी एह बार पार्टी कुल 240 सीट पर जीत हासिल कइल। भाजपा के समर्थक दल यानी राजग के कुल 292 सीट प्राप्त भइल।<ref>{{Cite web |date=2020-11-11 |title=Alliance Wise Election Live Results 2019: Lok Sabha Elections Result Live Alliance Wise, Party Wise |url=https://www.news18.com/lok-sabha-elections-2019/alliance-wise-tally-live-results/ |url-status=dead |archive-url=https://web.archive.org/web/20201111232305/https://www.news18.com/lok-sabha-elections-2019/alliance-wise-tally-live-results/ |archive-date=2020-11-11 |access-date=2024-12-16 |website=web.archive.org}}</ref> 9 जून 2024 के शपथ ग्रहण क के नरेन्द्र मोदी लगातार तीसरा बार प्रधानमन्त्री बनले। शपथ ग्रहण के बाद उ पहला आइसन अन कांग्रेसी प्रधानमंत्री बनले जे 1962 के बाद लगातार तीसरा बार प्रधानमंत्री बनले औरी उ  पंडित जवाहर लाल नेहरू के बराबरी कइले।<ref>{{Cite web |date=2020-12-18 |title=PM Narendra Modi Becomes Longest Serving Non-Congress Prime Minister |url=https://www.ndtv.com/people/pm-narendra-modi-becomes-longest-serving-non-congress-prime-minister-2279112 |url-status=live |archive-url=https://web.archive.org/web/20201218122430/https://www.ndtv.com/people/pm-narendra-modi-becomes-longest-serving-non-congress-prime-minister-2279112 |archive-date=2020-12-18 |access-date=2024-12-17 |website=web.archive.org}}</ref> मोदी आपन अगीला दु साल उत्तर औरी उत्तर-पूरब भारत के यात्रा में बितवले।<ref>{{Cite journal |date=2024-12-28 |title=Narendra Modi |url=https://en.m.wikipedia.org/wiki/Narendra_Modi#CITEREFMarino2014 |journal=Wikipedia |language=en}}</ref> साक्षात्कार में, उ स्वामी विवेकानन्द द्वारा स्थापित हिंदू आश्रम के दौरा करे के वर्णन बा: कलकता के पास बेलूर मठ, अल्मोडा में अद्वैत आश्रम औरी राजकोट में रामकृष्ण मिशन। प्रत्येक आश्रम में उनके प्रवास संक्षिप्त रहे काहेकी उनके पास आवश्यक कॉलेज शिक्षा के अभाव रहे। <ref>{{Cite web |title=Business News Today: Read Latest Business news, India Business News Live, Share Market & Economy News |url=https://economictimes.indiatimes.com/ |access-date=2024-12-28 |website=The Economic Times |language=en}}</ref> मोदी के जीवन में विवेकानन्द का बड़ा प्रभाव रहल बा।<ref>{{Cite book|url=https://doi.org/10.4324/9781315696584|title=Swami Vivekananda|date=2015-06-12|publisher=Routledge India|isbn=978-1-317-44637-8|editor-last=Paranjape|editor-first=Makarand R.}}</ref> 1968 के मध्य में, मोदी बेलूर मठ पहुचलन लेकिन उनकारा के लौटा दिहल गइल , जेकरा बाद उ सिलीगुड़ी औरी गुवाहाटी में रुकत  कलकत्ता, पश्चिम बंगाल औरी असम के दौरा किइलेन। एकरा बाद उ 1968 से 1969 तक दिल्ली औरी राजस्थान के रास्ता गुजरात लौटे से पहले, अल्मोडा में रामकृष्ण आश्रम गइलन, जहां उनके फेर से अस्वीकार कर दिहाल गइल। 1969 के अंत में चाहे 1970 के शुरुआत में, उ अहमदाबाद के खातिर फेर से रवाना होखे से पहीले एगो संक्षिप्त यात्रा के खातिर वडनगर लवातले। ,<ref>{{Cite journal |date=2024-12-30 |title=Narendra Modi |url=https://en.m.wikipedia.org/wiki/Narendra_Modi#CITEREFMarino2014 |journal=Wikipedia |language=en}}</ref><ref>{{Cite journal |date=2024-12-30 |title=Narendra Modi |url=https://en.m.wikipedia.org/wiki/Narendra_Modi#CITEREFPanda2016 |journal=Wikipedia |language=en}}</ref> जहां उ अपना चाचा के साथे रहत रहले औरी गुजरात राज्य सड़क परिवहन निगम में अपना चाचा के कैंटीन में काम करत रहले।<ref>{{Cite journal |date=2024-12-30 |title=Narendra Modi |url=https://en.m.wikipedia.org/wiki/Narendra_Modi#CITEREFMukhopadhyay2013 |journal=Wikipedia |language=en}}</ref> अहमदाबाद में, मोदी इनामदारी के साथे अपना परिचय के नवीनीकृत कइलान, जौन शहर में हेडगेवार भवन (आरएसएस मुख्यालय) में स्थित रहे। <ref>{{Cite web |date=1999-11-30 |title=The man behind Modi: Lakshmanrao Inamdar |url=https://www.indiatoday.in/magazine/the-big-story/story/20140519-narendra-modi-lakshmanrao-inamdar-rss-802639-1999-11-29 |access-date=2025-01-02 |website=India Today |language=en}}</ref><ref>{{Cite news |last=Mukhopadhyay |first=Nilanjan |date=2014-05-14 |title=Narendra Modi: The making of the political leader |url=http://www.livemint.com/Politics/9NIOOTsZCFWnuFX5OO9XNL/Narendra-Modi-The-making-of-the-political-leader.html |access-date=2025-01-02 |work=mint |language=en}}</ref><ref>{{Cite journal |date=2025-01-01 |title=Narendra Modi |url=https://en.m.wikipedia.org/wiki/Narendra_Modi#CITEREFMarino2014 |journal=Wikipedia |language=en}}</ref> एगो वयस्क के रूप में मोदी के पहला ज्ञात राजनीतिक गतिविधि 1971 में रहे जब उ बांग्लादेश मुक्ति संग्राम में लडे खातिर अटल बिहारी वाजपेयी के नेतृत्व में दिल्ली में जनसंघ के सत्याग्रह में शामिल भईल रहले<ref>{{Cite web |title=A Sataygrah and Asatyagraha: Narendra Modi and the Liberation of Bangladesh |url=https://thewire.in/history/a-sataygrah-and-asatyagraha-narendra-modi-and-the-liberation-of-bangladesh |access-date=2025-01-02 |website=The Wire |language=en}}</ref><ref>{{Cite web |last=Lahiri |first=Ishadrita |date=2021-03-27 |title=Was Modi Arrested for Bangladesh Satyagraha? Here’s What We Know |url=https://www.thequint.com/news/politics/was-modi-arrested-for-bangladesh-satyagraha-in-1971-here-is-what-we-know#read-more |access-date=2025-01-02 |website=TheQuint |language=en}}</ref> इंदिरा गांधी के नेतृत्व वाला केंद्र सरकार मुक्ति वाहिनी के खुला समर्थन पे रोक लगा दिहाल; मोदी के अनुसार, उनके कुछ समय खातिर तिहाड़ जेल में रखल गइल रहे। <ref>{{Cite web |date=2021-03-27 |title=Delhi confidential: The Satyagraha |url=https://indianexpress.com/article/delhi-confidential/narendra-modi-satyagraha-bangladesh-liberation-fighters-delhi-confidential-7247247/ |access-date=2025-01-03 |website=The Indian Express |language=en}}</ref><ref>{{Cite web |title=Political slugfest over PM Modi’s ‘Satyagraha for Bangladesh’ remarks |url=https://www.tribuneindia.com/news/nation/political-slugfest-over-pm-modis-satyagraha-for-bangladesh-remarks-230844 |access-date=2025-01-03 |website=The Tribune |language=en}}</ref><ref>{{Cite news |date=2021-03-27 |title=Modi's satyagraha talk in Dhaka sparks online war |url=https://timesofindia.indiatimes.com/india/modis-satyagraha-talk-in-dhaka-sparks-online-war/articleshow/81717548.cms |access-date=2025-01-03 |work=The Times of India |issn=0971-8257}}</ref> 1971 के भारत-पाकिस्तान युद्ध के बाद, मोदी  अपना चाचा के नौकरी छोड़ दिहाले और आरएसएस के पूर्णकालिक प्रचारक बन गइले,<ref name="Narendra Modi">{{Cite journal |date=2025-01-03 |title=Narendra Modi |url=https://en.m.wikipedia.org/wiki/Narendra_Modi#CITEREFMukhopadhyay2013 |journal=Wikipedia |language=en}}</ref> इनामदार के अधीन काम करते हुए। <ref name="Narendra Modi"/> लड़ाई से कुछ समय पहीले, मोदी  भारत सरकार के खिलाफ नई दिल्ली में एगो अहिंसक विरोध प्रदर्शन में भाग लिहाले, जेकरा खातिर उनका के गिरफ्तार कर लिहाल गइल; इ गिरफ्तारी के कारण, इनामदार  मोदी के मार्गदर्शन करे के निर्णय लिहाल।<ref>{{Cite journal |date=2025-01-04 |title=Narendra Modi |url=https://en.m.wikipedia.org/wiki/Narendra_Modi#CITEREFMarino2014 |journal=Wikipedia |language=en}}</ref> मोदी के अनुसार, उ एगो आइसन सत्याग्रह के हिस्सा रहले जौना के कारण राजनीतिक लड़ाई भइल।<ref>{{Cite web |title=Political slugfest over PM Modi’s ‘Satyagraha for Bangladesh’ remarks |url=https://www.tribuneindia.com/news/nation/political-slugfest-over-pm-modis-satyagraha-for-bangladesh-remarks-230844 |access-date=2025-01-05 |website=The Tribune |language=en}}</ref><ref>{{Cite journal |date=2024-11-30 |title=Prime Minister's Office (India) |url=https://en.m.wikipedia.org/wiki/Prime_Minister's_Office_(India) |journal=Wikipedia |language=en}}</ref> 1978 में, मोदी  दिल्ली विश्वविद्यालय के स्कूल ऑफ ओपन लर्निंग<ref>{{Cite web |date=2017-03-14 |title=Narendra Modi degree row: DU college says it has no data of students passing out in 1978 |url=https://www.indiatoday.in/india/story/narendra-modi-degree-controversy-delhi-university-rti-965536-2017-03-14 |access-date=2025-01-05 |website=India Today |language=en}}</ref> से राजनीति विज्ञान में कला स्नातक (बीए) के डिग्री प्राप्त काइले। <ref>{{Cite news |date=2016-05-10 |title=Narendra Modi’s degree ‘authentic’: Delhi University Registrar Tarun Das |url=https://www.thehindu.com/news/cities/Delhi/Narendra-Modi%E2%80%99s-degree-%E2%80%98authentic%E2%80%99-Delhi-University-Registrar-Tarun-Das/article60504479.ece |access-date=2025-01-07 |work=The Hindu |language=en-IN |issn=0971-751X}}</ref><ref>{{Cite web |date=2013-09-13 |title=Narendra Modi: From tea vendor to PM candidate |url=https://www.indiatoday.in/india/north/story/narendra-modi-from-tea-vendor-to-pm-candidate-210939-2013-09-12 |access-date=2025-01-07 |website=India Today |language=en}}</ref> 1983 में, उ गुजरात विश्वविद्यालय से राजनीति विज्ञान में मास्टर ऑफ आर्ट्स (एमए) के डिग्री प्राप्त कइलन, और बाहरी दूरस्थ शिक्षा के छात्र के रूप में प्रथम श्रेणी में स्नातक के उपाधि प्राप्त कइलान <ref>{{Cite web |date=2014-08-19 |title=Modi proves to be an astute strategist - Hindustan Times |url=http://www.hindustantimes.com/india-news/modi-proves-to-be-an-astute-strategist/article1-264941.aspx |access-date=2025-01-07 |website=web.archive.org |archive-date=2014-08-19 |archive-url=https://web.archive.org/web/20140819083702/http://www.hindustantimes.com/india-news/modi-proves-to-be-an-astute-strategist/article1-264941.aspx |url-status=dead }}</ref><ref>{{Cite news |last=Avasthi |first=Yogesh |date=2016-05-10 |title=Globetrotting PM Modi was weakest in 'International Relations' |url=https://economictimes.indiatimes.com/news/politics-and-nation/globetrotting-pm-modi-was-weakest-in-international-relations/articleshow/52200607.cms |access-date=2025-01-07 |work=The Economic Times |issn=0013-0389}}</ref>।<ref>{{Cite web |date=2016-05-02 |title=Degree row- PM Modi MA with first class: Gujarat University |url=https://indianexpress.com/article/india/india-news-india/pm-modi-scored-62-3-in-ma-from-gujarat-univerisity-vc-2779138/ |access-date=2025-01-07 |website=The Indian Express |language=en}}</ref> उनके बीए और एमए डिग्रि के प्रामाणिकता के लेके विवाद बा।<ref>{{Cite web |last=Team |first=N. L. |date=2020-10-03 |title=The curious case of TOI's report on Modi's 'dear friend' |url=https://www.newslaundry.com/2020/10/03/the-curious-case-of-tois-report-on-modis-dear-friend |access-date=2025-01-07 |website=Newslaundry |language=en}}</ref><ref>{{Cite web |last=Bhattacharya |first=Tathagata |date=2018-06-16 |title=The curious case of Prime Minister Narendra Modi’s degree |url=https://www.nationalheraldindia.com/india/the-curious-case-of-prime-minister-narendra-modis-degree |access-date=2025-01-07 |website=National Herald |language=en}}</ref> <ref>{{Cite journal |date=2024-11-27 |title=The Caravan |url=https://en.m.wikipedia.org/wiki/The_Caravan |journal=Wikipedia |language=en}}</ref> द इकोनॉमिक टाइम्स के अनुसार, मोदी के इरादा अपना पाठक लोगन के आरएसएस की कार्यप्रणाली समझावेके रहे, औरी आरएसएस के सदस्य के आश्वस्त करे के रहे कि उ वैचारिक रूप से उनके साथे जुड़ल रहियन। प्रधान मंत्री बनले के बाद उ एग्जाम वॉरियर्स नाम के एगो पुस्तक भी लिखले, जौन बच्चा सब के खातिर सराहनीय परीक्षा के सामना करे खातिर एगो मार्गदर्शिका रहे। मोदी  आठ गो अन्य किताब भी लिखलन , जेमें ज्यादातर बच्चा सब के खातिर लघु कहानि बा। <ref>{{Cite news |date=2008-04-09 |title=Jyotipunj: Narendra Modi writes on 'my organisation, my leaders' |url=https://economictimes.indiatimes.com/news/politics-and-nation/jyotipunj-narendra-modi-writes-on-my-organisation-my-leaders/articleshow/2936428.cms |access-date=2025-01-19 |work=The Economic Times |issn=0013-0389}}</ref> मोदी  दूंगों गुजराती गरबा गीत के बोल लिखले बाड़े; गरबो, ध्वनि भानुशाली द्वारा गावल गइल औरी तनिष्क बागची द्वारा संगीतबद्ध कइल गइल, औरी माडी, दिव्या कुमार द्वारा गावल गइल और मीत ब्रदर्स द्वारा संगीतबद्ध कइल गइल। गाने 2023 में शरद ऋतु नवरात्रि के अवसर पर जारी कइल गइल रहे। <ref>{{Cite journal |date=2025-01-18 |title=Narendra Modi |url=https://en.m.wikipedia.org/wiki/Narendra_Modi#cite_note-559 |journal=Wikipedia |language=en}}</ref><ref>{{Cite book|url=https://doi.org/10.1093/oso/9780190912468.003.0008|title=Business-Friendly Gujarat Under Narendra Modi|last=Jaffrelot|first=Christophe|date=2019-01-02|publisher=Oxford University Press|isbn=978-0-19-091246-8|pages=211–233}}</ref> एबंडेंस इन मिलेट्स, फालू औरी गौरव शाह के एगो गाना, जेमे बाजरा के प्रचार खातिर मोदी द्वारा दिहल गइल भाषण रहे, के 2024 ग्रैमी अवार्ड्स खातिर सर्वश्रेष्ठ वैश्विक संगीत प्रदर्शन श्रेणी में नामांकन मिलल।<ref>{{Cite web |title=Song Featuring PM Modi Nominated For Grammy Awards |url=https://www.ndtv.com/india-news/song-featuring-pm-narendra-modi-nominated-for-grammy-awards-4565922 |access-date=2025-01-21 |website=www.ndtv.com |language=en}}</ref><ref>{{Cite web |title=2024 GRAMMYs: See The Full Winners & Nominees List {{!}} GRAMMY.com |url=https://www.grammy.com/news/2024-grammys-nominations-full-winners-nominees-list |access-date=2025-01-21 |website=grammy.com}}</ref> * 41वां लोकमान्य तिलक राष्ट्रीय पुरस्कार 2023 से प्रधानमंत्री नरेंद्र मोदी के सम्मानित कइल गइल। * 2014 के लोकसभा चुनाव में भारतीय जनता पार्टी के नेतृत्व वाला राष्ट्रीय जनतांत्रिक गठबंधन (एनडीए) के भारी जीत के बाद, मोदी  26 मई 2014 के भारत के प्रधान मंत्री के रूप में शपथ लीहले, उ देश के आजादी के बाद पैदा होने वाला पहिला भारतीय प्रधान मंत्री बन गइले। * 2001 में, केशुभाई पटेल के स्वास्थ्य ख़राब चलत रहे और भाजपा उप-चुनावों में कुछ राज्य विधानसभा सीट हार गइल। सत्ता के दुरुपयोग, भ्रष्टाचार और खराब प्रशासन के आरोप लगा वल गइल औरी 2001 में भुज में आइल भूकंप से निपटे खातिर उनके प्रशासन के तरीका से पटेल के प्रतिष्ठा के नुकसान पहुंचल रहे। == निजी जीवन == नरेंद्र मोदी के जाति के पारंपरिक परंपरा के अनुसार, उनकर परिवार  जशोदाबेन चिमनलाल मोदी के साथ सगाई कर दिहाल, जेकरा बाद उनकर शादी तब भइल जब उ 17 बरिस के रहली औरी  इ  18 बरिस के रहले।<ref name=":0">{{Cite book|url=https://doi.org/10.5149/northcarolina/9781469629537.003.0003|title=The Child Was to Be His Wife|last=Syrett|first=Nicholas L.|date=2016-10-03|publisher=University of North Carolina Press|isbn=978-1-4696-2953-7}}</ref><ref>{{Cite news |date=2018-02-07 |title=Narendra Modi’s Estranged Wife Escapes Unhurt From Car Crash in India (Published 2018) |url=https://www.nytimes.com/2018/02/07/world/asia/india-narendra-modi-wife.html |access-date=2024-12-27 |language=en}}</ref> एके तुरंत बाद, उ अपन पत्नी के छोड़ दिहाले।<ref name=":0" /> और घर छोड़ दिहाले। इ जोडा कबो तलाक ना लिहल लेकिन कई दशक तक उनके सार्वजनिक घोषणा में इ वियाह के जिक्र ना रहे।<ref>{{Cite news |date=2018-02-07 |title=Narendra Modi’s Estranged Wife Escapes Unhurt From Car Crash in India (Published 2018) |url=https://www.nytimes.com/2018/02/07/world/asia/india-narendra-modi-wife.html |access-date=2024-12-27 |language=en}}</ref> अप्रैल 2014 में, राष्ट्रीय चुनाव से कुछ समय पहीले, जेमें उ सत्ता हासिल कइले, मोदी सार्वजनिक रूप से पुष्टि काइले कि उ शादीशुदा हाउवान और उनकर पत्नी जशोदाबेन हइ।<ref>{{Cite web |date=2018-09-13 |title=Narendra Modi Fast Facts - CNN |url=https://edition.cnn.com/2015/01/19/world/narendra-modi-fast-facts/index.html |url-status=live |archive-url=https://web.archive.org/web/20180913201412/https://edition.cnn.com/2015/01/19/world/narendra-modi-fast-facts/index.html |archive-date=2018-09-13 |access-date=2024-12-27 |website=सीएनएन}}</ref> मोदी के एगो जीवनी लेखक ने लिखल बा कि मोदी  शादी के गुप्त रखल काहेकी उ आरएसएस में प्रचारक ना बन पवते, जेकरा खातिर कबों ब्रह्मचर्य के आवश्यकता हत रहे।<ref>{{Cite web |date=2013-09-13 |title=Narendra Modi: From tea vendor to PM candidate |url=https://www.indiatoday.in/india/north/story/narendra-modi-from-tea-vendor-to-pm-candidate-210939-2013-09-12 |access-date=2024-12-27 |website=इंडिया टुडे |language=en}}</ref> == गुजरात के मुख्यमंत्री के रूप में == 2001 में केशुभाई पटेल (तत्कालीन मुख्यमंत्री) के सेहत बिगड़े लगल रहे औरी भाजपा चुनाव में काइगो सीट हारत रहे।<ref>{{Cite book|url=https://books.google.com/books?id=qT7QvviGoJsC&pg=PA116|title=Business Standard Political Profiles of Cabals and Kings|date=2009|publisher=Business Standard Books|isbn=978-81-905735-4-2|language=en}}</ref> एकरा बाद भाजपा के राष्ट्रीय अध्यक्ष मुख्यमंत्री के रूप में मोदी के नया उम्मीदवार के रूप में रखले । हालांकि भाजपा के नेता लालकृष्ण आडवाणी, मोदी के सरकार चलावे के अनुभव के कमी के कारण चिंतित रहले। मोदी  पटेल के उप मुख्यमंत्री बनले के प्रस्ताव के ठुकरा दिहाले औरी आडवाणी व अटल बिहारी वाजपेयी से कहके कि यदि गुजरात के जिम्मेदारी देवेके बा त पूरा दीहल जाओ अन्यथा ना दिहाल जाओ। 3 अक्टूबर 2001 के इ केशुभाई पटेल के जगह गुजरात के मुख्यमंत्री बनले। एकरा साथ ही उनका पर दिसम्बर 2002 में होखे वाला चुनाव के पूरा जिम्मेदारी भी रहे।<ref>{{Cite web |title=Narendra Modi – Leading the race to 7 RCR |url=https://zeenews.india.com/news/general-elections-2014/pm-candidates/narendra-modi-leading-the-race-to-7-rcr_921104.html |access-date=2024-06-02 |website=Zee News |language=en}}</ref> सुरुआती जीवन आ शिक्षा == नरेंद्र मोदी के जनम 17 सितंबर 1950 के एगो गुजराती परिवार में, मेहसाणा जिला (ओह समय के बांबे स्टेट, अब गुजरात) के वडनगर में भइल। बाबूजी दामोदरदास मूलचंद मोदी (c.1915 - 1989) आ महतारी हीराबेन मोदी (जनम c.1920) के छह गो में नरेंद्र मोदी तिसरा संतान रहलें।{{sfn|Marino|2014|pp=13, 15, 29–30, 74}} मोदी के परिवार मोध-घाँची-तेली समुदाय के हवे,<ref>{{Cite news|url=http://www.dnaindia.com/india/report-narendra-modi-belongs-to-modh-ghanchi-caste-which-was-added-to-obcs-categories-in-1994-says-gujarat-government-1986389|title=Narendra Modi belongs to Modh-Ghanchi caste, which was added to OBCs categories in 1994, says Gujarat government {{!}} Latest News & Updates at Daily News & Analysis|date=9 May 2014|work=dna|access-date=19 May 2017|language=en-US}}</ref><ref>{{Cite news|url=http://indianexpress.com/article/india/india-news-india/pm-modi-brother-asks-teli-community-to-adopt-modi-prefix-4386125/|title=PM Modi's brother asks Teli community to adopt 'Modi' prefix|date=20 November 2016|work=The Indian Express|access-date=19 May 2017|language=en-US}}</ref><ref>{{Cite news|url=http://timesofindia.indiatimes.com/city/ahmedabad/Modi-is-a-Teli-Ghanchi-OBC-BJP/articleshow/34084111.cms|title='Modi is a Teli-Ghanchi OBC': BJP - Times of India|work=दि टाइम्स ऑफ इंडिया|access-date=19 May 2017}}</ref> जेकरा के सरकारी तौर पर अन्य पिछड़ा वर्ग (ओबीसी) में गिनल जाला।<ref name=" TOI Modh-Ghanchi-Teli">{{cite news|url=http://timesofindia.indiatimes.com/city/ahmedabad/Modi-is-a-Teli-Ghanchi-OBC-BJP/articleshow/34084111.cms|title= 'Modi is a Teli-Ghanchi OBC': BJP|work=दि टाइम्स ऑफ इंडिया|date=23 April 2014|accessdate=17 February 2017}}</ref><ref>{{Cite news|url=http://www.business-standard.com/article/opinion/the-office-of-prime-minister-a-largely-north-indian-upper-caste-hindu-affair-114050700846_1.html|title=The office of Prime Minister: A largely north Indian upper-caste, Hindu affair|last=Ghai|first=Rajat|date=7 May 2014|work=Business Standard India|access-date=19 May 2017}}</ref> लरिकाईं में मोदी वडनगर रेलवे टीशन पर चाय बेचे में अपने बाबूजी के मदद करें, बाद में अपना भाई के साथे खुद के छाया के दुकान बस अड्डा के लगे चलवलें।<ref>{{Cite news|url=http://www.oneindia.com/international/i-have-special-association-with-tea-modi-tells-lankan-tamils-2433622.html|title='I have a special association with tea', Modi tells Lankan Tamils|work=www.oneindia.com|access-date=19 May 2017|language=en}}</ref><ref>{{Cite book|url=https://books.google.co.in/books?id=ALWaDAAAQBAJ&lpg=PA1&pg=PA20#v=onepage&q&f=false|title=High School Essays|last=Sinha|first=N. C.|publisher=|year=|isbn=978-93-5186-604-6|location=|pages=20|language=en}}</ref> मोदी साल 1967 में वडनगर से इंटर के इम्तिहान पास कइलें, जहाँ उनके एगो अध्यापक औसत दर्जा के बिद्यार्थी आ बाद-बिबाद में रूचि रखे वाला आ थियेटर में लगाव रखे वाला बिद्यार्थी के रूप में बतवलें।<ref name="Jose Caravan">{{cite news|url=http://www.caravanmagazine.in/reportage/emperor-uncrowned?page=1,1|title=The Emperor Uncrowned|last=Jose|first=Vinod K.|date=1 मार्च 2012|work=The Caravan|pages=2–4|authorlink=:en:Vinod Jose|accessdate=11 April 2013|archive-date=23 September 2015|archive-url=https://web.archive.org/web/20150923201924/http://www.caravanmagazine.in/reportage/emperor-uncrowned?page=1,1|url-status=dead}}</ref> बाद-बिबाद प्रतियोगिता में मोदी के कुशलता उनके संघे पढ़े वाला बिद्यार्थी आ उनके मास्टर साहब लोग चिन्हित कइल।{{sfn|Marino|2014|p=16}} मोदी नाटकन (थियेटरन) में बहुत बिसाल व्यक्तित्व (लार्जर-देन-लाइफ) वाला किरदार निभावल पसंद करें जे उनके राजनीतिक कैरियर में भी झलके ला।{{sfn|Mukhopadhyay|2013|p=82}}<ref>{{cite news |url=http://www.newkerala.com/news/2014/fullnews-24257.html |title=Modi's life dominates publishing space (Election Special) |work=New Kerala |date=14 मार्च 2014 |accessdate=4 April 2014}}</ref> आठ बरिस के उमिर में मोदी के राष्ट्रीय स्वयंसेवक संघ (आरएसएस) के बारे में मालुम भइल आ ऊ लोकल शाखा में जाए सुरू कइलेन। एकरे बाद मोदी लक्ष्मणराव इनामदार, जिनके वकील साहब के नाँव से जानल जाय, मिलने आ उहे इनका के बालस्वयंसेवक बनवलें आ इनके राजनीतिक अभिभावक बनलें।{{sfn|Marino|2014 |p=24}} आरएसएस में अपना शिक्षा के दौरान मोदी वसंत गजेंद्रगाडकर आ नाथालाल जघड़ा से मिललें जे लोग भारतीय जनसंघ के नेता आ 1980 में भारतीय जनता दल के गुजरात इकाई के संस्थापक सदस्य रहल लोग। पढ़ाई के दौरान लरिकाईंये में इनके बियाह जसोदाबेन नरेंद्रभाई मोदी से तय हो गइल जेकरा के मोदी ना स्वीकार कइलें {{sfn|Marino|2014|p=21}} आ एही कारन उपजल पारिवारिक तनाव के कारन 1967 में घर छोड़ देवे के फैसला कइलें।{{sfn|Marino|2014|pp=22–23}} एकरे बाद मोदी दू साल उत्तर भारत आ [[पूर्बोत्तर भारत|पूर्वोत्तर भारत]] के जगह-जगह घुमलें, कवना-कवना जगह गइलें आ रहलें एकरे बारे में बहुत बिबरन ना मिले ला।{{sfn|Marino|2014|p=25}} अपना इंटरभ्यू में मोदी, स्वामी विवेकानंद के स्थापित बेलूर मठ (कलकत्ता लगे), एकरे बाद अल्मोड़ा के अद्वैत आश्रम आ रामकृष्ण मिशन, राजकोट जाए के बात कहले बाने। एह में से हर जगह कुछे दिन खातिर रह पवलें काहें कि उनके कॉलेज के शिक्षा अभिन पूरा ना भइल रहल।{{sfn|Marino|2014|pp=30–33}}{{sfn|Mukhopadhyay|2013|pp=128–129}}<ref>{{cite news|url=http://articles.economictimes.indiatimes.com/2014-05-26/news/50099180_1_narendra-modi-swami-vivekananda-belurmath|title=Narendra Modi invited to Ramakrishna Mission's headquarter in Belurmath|work=द इकॉनोमिक टाइम्स|date=26 मई 2014|accessdate=17 अक्टूबर 2014|archive-date=2014-10-31|archive-url=https://web.archive.org/web/20141031144902/http://articles.economictimes.indiatimes.com/2014-05-26/news/50099180_1_narendra-modi-swami-vivekananda-belurmath|url-status=dead}}</ref> विवेकानंद के इनके जीवन पर बहुत परभाव बतावल जाला।<ref name=ts0716>{{cite news |title=The tale of two Narendras: Narendra Modi and Swami Vivekananda |url=http://www.thestatesman.com/india/the-tale-of-two-narendras-narendra-modi-and-swami-vivekananda-152114.html|work=दि स्टेट्समैन |date=5 जुलाई 2016|accessdate=17 फरवरी 2017}}</ref> 1968 के गर्मी के सुरुआत में मोदी बेलूर मठ चहुँपलें बाकी इहाँ से वापस लवटा दिहल गइलें, जेकरा बाद कलकत्ता पच्छिम बंगाल आ आसाम में घूमलें, सिलीगुड़ी आ गौहाटी में रुकलें।{{sfn|Marino|2014|p=26}} मोदी अल्मोड़ा के रामकृष्ण आश्रम चहुँपलें आ एहू जा उनके ना लिहल गइल, एकरे बाद दिल्ली [[राजस्थान]] होखत वापस 1968–69 में गुजरात लवट अइलें।{{sfn|Marino|2014|p=27}} 1969 के अंत में या फिर 1970 के सुरुआत में मोदी कुछ दिन खातिर वडनगर अइलें आ एकरा बाद अहमदाबाद चल गइलें।{{sfn|Marino|2014|pp=28–29}} इहँवा मोदी अपने रिश्तेदार के इहाँ, गुजरात राज्य परिवहन कारपोरेशन के कैंटीन में काम करे लगलें।{{sfn|Mukhopadhyay|2013|p=131}}{{sfn|Mukhopadhyay|2013|p=138}} अहमदाबाद में मोदी दोबारा इनामदार से दोबारा मिललें जे ओह समय शहर के हेगडेवार भवन (शहर के आरएसएस मुख्यालय) में रहलें।<ref name="vakilsaheb">{{cite web|last=Unnithan|first=Sandeep|title=The man behind Modi: Lakshmanrao Inamdar|url=http://indiatoday.intoday.in/story/narendra-modi-lakshmanrao-inamdar-rss/1/359926.html|work=इंडिया टुडे |location=Ahmedabad |year=2014 |accessdate=22 May 2014}}</ref><ref name="livemint-modi-growth">{{cite web|last=Mukhopadhyay|first=Nilanjan|title=Narendra Modi: The making of the political leader|url=http://www.livemint.com/Politics/9NIOOTsZCFWnuFX5OO9XNL/Narendra-Modi-The-making-of-the-political-leader.html |year=2014 |accessdate=22 May 2014}}</ref>{{sfn | Marino | 2014 |p=35}} 1971 के भारत-पाक लड़ाई के बाद मोदी अपना चाचा के साथे काम कइल छोड़ के पूरा टाइम खातिर आरएसएस के प्रचारक बन गइलें{{sfn|Mukhopadhyay|2013|p=138}} आ इनामदार के मातहत काम करे लगलें।{{sfn|Marino|2014|pp=35–40}} लड़ाई के कुछे दिन पहिले मोदी भारत सरकार के खिलाफ एगो शांतिपूर्ण धरना-परदर्शन में हिस्सा लिहलें जेकरा खातिर उनके गिरफ्तार कइल गइल रहे; इहे कारन बतावल जाला कि इनामदार इनके सिखावे-पढ़ावे खातिर बीछलें।{{sfn|Marino|2014|pp=35–40}} कई साल बाद 2001 में इनामदार के जीवनी छपल जेह में मोदी सहायक लेखक रहलें।<ref>{{cite news |title=Mentoring Modi |first=Prateek |last=Goyal |date=18 May 2014 |url=http://www.punemirror.in/pune/cover-story/Mentoring-Modi/articleshow/35270364.cms |work=Pune Mirror |accessdate=21 जून 2015}}</ref> साल 1978 में मोदी राजनीतिशास्त्र में बीए कइलें,<ref>{{cite news|url=http://indiatoday.intoday.in/story/narendra-modi-degree-controversy-delhi-university-rti/1/903745.html |title=Narendra Modi degree row: DU college says it has no data of students passing out in 1978|work=[[इंडिया टुडे]] |accessdate=9 जून 2017}}</ref> दिल्ली विश्वविद्यालय के पत्राचार माध्यम से,{{sfn | Marino | 2014 |p=48}} आ थर्ड डिवीजन पास भइलें।<ref>{{cite news|url=http://www.thehindu.com/news/cities/Delhi/Narendra-Modi%E2%80%99s-degree-%E2%80%98authentic%E2%80%99-Delhi-University-Registrar-Tarun-Das/article14311846.ece |title=Narendra Modi’s degree ‘authentic’: Delhi University Registrar Tarun Das|work=[[दि हिंदू]] |accessdate=9 जून 2017}}</ref> पाँच बरिस बाद, 1982 में, गुजरात विश्वविद्यालय से [[राजनीति बिज्ञान]] में एमए के डिग्री लिहलें।<ref>{{cite web|last=Avasthi|first=Yogesh|url=http://economictimes.indiatimes.com/news/politics-and-nation/globetrotting-pm-modi-was-weakest-in-international-relations/articleshow/52200607.cms|title=Globetrotting PM Modi was weakest in 'International Relations'|accessdate=17 February 2017}}</ref><ref>{{cite news |url=http://www.hindustantimes.com/india-news/modi-proves-to-be-an-astute-strategist/article1-264941.aspx |title=Modi proves to be an astute strategist |work=Hindustan Times |date=23 December 2007 |accessdate=17 April 2014 |archive-date=19 August 2014 |archive-url=https://web.archive.org/web/20140819083702/http://www.hindustantimes.com/india-news/modi-proves-to-be-an-astute-strategist/article1-264941.aspx |url-status=dead }}</ref> 2008 में, मोदी  ज्योतिपुंज नाम के एगो गुजराती पुस्तक प्रकाशित कइलन, जेमें आरएसएस नेता लोगन के प्रोफाइल शामिल बा। सबसे लंबा समय तक एम. एस. गोलवलकर रहले, जेकरा नेतृत्व में आरएसएस के विस्तार भईल औरी जिनका के मोदी पूज्य श्री गुरुजी ("पूजा के योग्य गुरु") के रूप में संदर्भित करतरन। ==नोट== <references group="नोट"/> ==संदर्भ== {{Reflist|30em}} ==बाहरी कड़ी== {{Commons category|Narendra Modi}} ;ऑफिशियल * [http://pmindia.gov.in/en/ Official website (Prime Minister's office)] * {{Official website|http://www.narendramodi.in}} (personal) * [http://www.bjp.org/leadership/shri-narendra-modi Official page on Bharatiya Janata Party's website] {{Webarchive|url=https://web.archive.org/web/20181222221309/http://www.bjp.org/leadership/shri-narendra-modi |date=2018-12-22 }} {{s-start}} {{s-off}} {{s-bef|before=केशुभाई पटेल}} {{s-ttl|title=गुजरात के मुख्यमंत्री|years=2001–2014}} {{s-aft|after=आनंदीबेन पटेल}} |- {{s-bef|before=[[मनमोहन सिंह]]}} {{s-ttl|title=[[भारत के प्रधानमंत्री]]|years=2014–वर्तमान}} {{s-inc}} |- {{s-par|in-lwr}} {{s-bef|before=[[मुरली मनोहर जोशी]]}} {{s-ttl|title=लोकसभा सांसद<br />बनारस से|years=2014–वर्तमान}} {{s-inc}} {{s-end}} {{Authority control}} [[श्रेणी:1950 में जनम]] [[श्रेणी:16वीं लोकसभा के सदस्य]] [[श्रेणी:गुजराती लोग]] [[श्रेणी:जियत लोग]] [[श्रेणी:उत्तर प्रदेश से लोकसभा सांसद]] [[श्रेणी:नरेंद्र मोदी|*]] [[श्रेणी:भारत के परधानमंत्री]] evtgxugl89jaqjcbsg47fx1qs1helkz गोदावरी नदी 0 9319 803338 796221 2026-07-29T16:21:53Z SM7Bot 7808 /* बान्ह */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803338 wikitext text/x-wiki {{Geobox|River <!-- *** Name section *** --> | name = गोदावरी | native_name = | other_name = दक्षिण गंगा <!-- *** Image *** ---> | image = Dummugudem Barrage on Godavari Khammam District.jpg | image_size = 300 | image_caption = गोदावरी पर बनल एगो बंधा <!-- *** Country etc. *** --> | country = [[भारत]] | state = [[महाराष्ट्र]] | state1 = [[तेलंगाना]] | state2 = [[छत्तीसगढ़]] | state3 = [[आंध्र प्रदेश]] | state4 = [[पांडिचेरी]] ([[यनम, पांडिचेरी|यनम]]) | region = [[दक्खिन भारत]], [[पच्छिम भारत]] | city1 = [[नाशिक]] | city2 = [[गंगाखेड़]] | city3 = [[नांदेड़]] | city4 = [[रामागुंडम]] | city5 = [[भद्राचलम]] | city6 = [[राजमुंद्री]] <!-- *** Geography *** --> | length = 1465 | length_round = 0 | watershed = 312812 | watershed_round = 0 | discharge1_location = पोलावरम (1901–1979) | discharge1_average = 3061.18 | discharge1_max = 34606 | discharge1_min = 7 | discharge1_note = <ref>{{cite web|title=Sage River Database |url=http://www.sage.wisc.edu/riverdata/scripts/station_table.php?qual=32&filenum=865 |accessdate=16 जून 2011 |url-status=dead|archiveurl=https://web.archive.org/web/20100621153903/http://www.sage.wisc.edu/riverdata/scripts/station_table.php?qual=32&filenum=865 |archivedate=21 जून 2010 }}</ref> | discharge_location = | discharge_average = 3505 | discharge_note = <ref>{{cite journal | last = कुमार | first = राकेश | last2 = सिंह | first2 = आर. डी. | last3 = शर्मा | first3 = के. डी. | title = Water Resources of India | trans-title = भारत के जल संसाधन | journal = करेंट साइंस | volume = 89 | issue = 5 | pages = 794–811 | publisher = Current Science Association | location = बंगलौर | date = 10 सितंबर 2005 | url = http://www.currentscience.ac.in/Downloads/article_id_089_05_0794_0811_0.pdf | accessdate = 13 अक्टूबर 2013 | archive-date = 2019-01-06 | archive-url = https://web.archive.org/web/20190106043752/http://www.currentscience.ac.in/Downloads/article_id_089_05_0794_0811_0.pdf%20 | url-status = dead }}</ref> <!-- *** Source *** --> | source_name = | source_location = ब्रह्मगिरि पहाड़ी, [[त्र्यंबकेश्वर]] | source_district = [[नासिक]] | source_region = | source_state = [[महाराष्ट्र]] | source_country = [[भारत]] | source_coordinates = {{coord|19|55|48|N|73|31|39|E|display=inline}} | source_elevation = 920 | source_length = <!-- *** Mouth *** --> | mouth_name = | mouth_location = [[अंतर्वेदी]], [[बंगाल के खाड़ी]] में | mouth_district = [[पूर्ब गोदावरी जिला|पूर्ब गोदावरी]] | mouth_region = | mouth_state = [[आंध्र प्रदेश]] | mouth_country = [[भारत]] | capital_coordinates = | mouth_coordinates = {{coord|17|0|N|81|48|E|display=inline,title}} | mouth_coordinates_note = <ref>{{GEOnet2|32FA87A287373774E0440003BA962ED3|Godāvari River}}</ref> | mouth_elevation = 0 <!-- *** Tributaries*** --> | tributary_left = [[बाणगंगा (महाराष्ट्र)|बाणगंगा]] | tributary_left1 = [[कादवा नदी|कादवा]] | tributary_left2 = [[शिवना नदी|शिवना]] | tributary_left3 = [[पूर्णा नदी(गोदावरी थाला)|पूर्णा]] | tributary_left4 = [[कदम नदी|कदम]] | tributary_left5 = [[प्राणहिता नदी|प्राणहिता]] | tributary_left6 = [[इंद्रावती नदी|इंद्रावती]] | tributary_left7 = [[तालिपेरु नदी|तालिपेरु]] | tributary_left8 = [[सबरी नदी|सबरी]] | tributary_right = [[नासार्दी नदी|नासार्दी]] | tributary_right1 = [[दरना नदी|दरना]] | tributary_right2 = [[प्रवरा नदी|प्रवरा]] | tributary_right3 = [[सिंदफाना नदी|सिंदफाना]] | tributary_right4 = [[मांजरा नदी|मांजरा]] | tributary_right5 = [[मनेर नदी|मनेर]] | tributary_right6 = [[किन्नारासनी नदी|किन्नारासनी]] | tributary_right7 = <!-- *** Free free fields *** --> | free_name = | free_value = <!-- *** Map section *** --> | map = Godavari River.png | map_size = 300 px | map_caption = गोदावरी के रास्ता के नक्शा <!-- *** Website *** --> | website = | commons = <!-- *** Footnotes *** --> | footnotes = }} '''गोदावरी''' नदी [[दक्खिन भारत]] में बहे वाली एगो महत्वपूर्ण नदी ह आ प्रायदीपी भारत के सभसे बड़ थाला वाली नदी ह। ई [[महाराष्ट्र]] से निकल के [[बंगाल के खाड़ी]] में गिरे ले। गोदावरी नदी भारत के दूसरी सबसे लंबी नदी ह, गंगा नदी के बाद। एकरा निकसे के सोता महाराष्ट्र के त्रयंबकेश्वर में बा।<ref>{{Cite web |url=http://www.kgbo-cwc.ap.nic.in/About%20Basins/Godavari.pdf |title="Godavari river basin map" |access-date=2018-06-22 |archive-date=2013-10-12 |archive-url=https://web.archive.org/web/20131012032632/http://www.kgbo-cwc.ap.nic.in/About%20Basins/Godavari.pdf |url-status=dead }}</ref> इहाँ से निकसे के बाद ई {{convert|1465|km|mi}} के दूरी तय करे ले जेकरे दौरान ई महाराष्ट्र (48.6 %), [[तेलंगाना]] (18.8 %), [[आंध्र प्रदेश]] (4.5 %), [[छत्तीसगढ़]] (10.9 %), [[मध्य प्रदेश]] (10.0 %), [[ओडिशा]] (5.7 %), [[कर्नाटक]] (1.4 %) आ [[पुद्दुचेरी]] ([[यनम जिला|यनम]]) से हो के गुजरे ले आ अंत में [[बंगाल के खाड़ी]] में अपने सहायिका सभ के बिसाल नेटवर्क से एकट्ठा कइल पानी गिरावे ले।<ref>{{cite web |url=http://www.cwc.nic.in/main/webpages/hba.pdf |title=Integrated Hydrological DataBook (Non-Classified River Basins) |publisher=Central Water Commission |accessdate=2015-10-13 |page=9 |archive-date=2016-03-04 |archive-url=https://web.archive.org/web/20160304192920/http://www.cwc.nic.in/main/webpages/hba.pdf |url-status=dead }}</ref> एक थाला (बेसिन) लगभग {{convert|3,12,812|km2|sqmi|abbr=on}} एरिया पर बिस्तार लिहले बा आ एह तरीका से ई भारतीय उपमहादीप के सभसे बिसाल नदी थाला सभ में से एक हवे; खाली गंगा आ सिंधु नदी के थाला एकरा ले बड़हन बाने।<ref>{{cite web|url=http://india-wris.nrsc.gov.in/wrpinfo/index.php?title=Basins |title=Basins |language=en |publisher=India-wris.nrsc.gov.in |date= |accessdate=2018-09-22}}</ref> लंबाई, थाला के साइज आ कुल पानी के बहाव, सभ मामिला में ई प्रायदीपी भारत के सभसे बड़ नदी ठहरे ले; मुला एही कारन एकरा के '''दक्खिन गंगा''' के उपाधि दिहल गइल होखी।<ref>{{cite web |url=http://www.importantindia.com/10222/dakshina-ganga/ |title=Dakshina Ganga (Ganga of South India) – River Godavari |publisher=Important India |accessdate=2015-10-21 |archive-date=2016-01-18 |archive-url=https://web.archive.org/web/20160118171303/http://www.importantindia.com/10222/dakshina-ganga/ |url-status=dead }}</ref> हिंदू ग्रंथ सभ एह नदी के बरनन सदियन से मिले ला आ एकरे थाला में बहुत समृद्ध संस्कृति के बिकास भइल बा। पछिला कुछ दशक में, नदी के धारा रोक के कई गो बंधा बनावल गइल बाड़ें जिनहन से एकर बहाव कम भइल बा। एह नदी के डेल्टा वाला इलाका में जनघनत्व 729 ब्यक्ति/किमी<sup>2</sup> — देस के औसत के लगभग दुन्ना बाटे, आ ई डेल्टाई इलाका ख़तरा में भी बतावल जा रहल बा अगर [[समुंद्र तल]] में उठान होखे।<ref>http://www.igbp.net/download/18.62dc35801456272b46d4b/1398850074082/NL82-Deltas_infographic.pdf</ref><ref>{{cite web |url=http://www.indiaenvironmentportal.org.in/files/file/Shrinking_and_sinking_delta_major_role_of_Dams_May_2014.pdf |last=South Asia Network on Dams Rivers and People |title=Shrinking and Sinking Deltas: Major role of Dams in delta subsidence and effective sea level rise |year=2014 |accessdate=15 जनवरी 2016 |archive-date=2016-02-06 |archive-url=https://web.archive.org/web/20160206060736/http://www.indiaenvironmentportal.org.in/files/file/Shrinking_and_sinking_delta_major_role_of_Dams_May_2014.pdf |url-status=dead }}</ref> == बहाव मारग == गोदावरी नदी [[महाराष्ट्र]] में [[नासिक]] के लगे [[पच्छिमी घाट]] के पहाड़ी से निकसे ले, एकर उदगम से अरब सागर बस के दूरी पर बाटे। ई नदी कुल के लंबाई तय करे ले, पहिले पूरुब मुँह के दक्कन के पठार में, फिर दक्खिन-पूरुब मुँह के हो जाले आ [[आंध्र प्रदेश]] के [[पच्छिम गोदावरी जिला]] आ [[पूर्ब गोदावरी जिला]] में प्रवेश करे ले, एकरा बाद आगे बढ़ के ई दू गो मुख्य धारा में बँट जाले आ बिसाल डेल्टा बनावे आ [[राजमुंद्री]] के लगे [[डोलेश्वरम बैराज]] के आगे [[बंगाल के खाड़ी]] में गिर जाले। गोदावरी नदी के [[नदी थाला|थाला (बेसिन)]] के कुल रकबा करीबन बाटे , ई भारत देस के कुल रकबा के लगभग दसवाँ हिस्सा बाटे आ [[यूनाइटेड किंगडम]] आ [[आयरलैंड रिपब्लिक|आयरलैंड]] के मिला के दुनों के बरोबर बाटे। नदी के लंबाई के तीन खंड में बाँटल जाला: * ऊपरी (मांजरा नदी से संगम से पहिले) * बिचला (मांजरा के संगम से [[प्राणहिता नदी|प्राणहिता]] के संगम ले) * निचला (प्राणहिता के संगम के बाद) ई तीनों हिस्सा मिला के नदी के कुल बेसिन के 24.2 % हिस्सा होखे ला। एह नदी के सालाना बहाव बाटे आ लगभग आधा हिस्सा के बिबिध प्रोजेक्ट सभ द्वारा लाभ खातिर इस्तेमाल कइल जा रहल बाटे (जइसे कि पनबिजली खातिर)। एह नदी के पानी के राज्यन के बीचा में (रिपरियन स्टेट्स) बँटवारा गोदावरी वाटर डिस्प्यूट ट्रिब्यूनल द्वारा होखे ला। नदी के बहाव में बाढ़ वाला बहाव भारत के सभसे बेसी बाढ़ बहाव वालन में से एक हवे, 1986 में बाढ़ में ई बहाव 3.6 मिलियन क्यूसेक रिकार्ड कइल गइल रहे, 1 मिलियन क्यूसेक सालान नॉर्मल बाढ़ के बहाव होखे ला। == सहायिका == गोदावरी नदी के मुख्य सहायिका नदी सभ के दू हिस्सा में बाँटल जा सके ला: बायें से आ के एह में पूर्णा, प्राणहिता, इंद्रावती आ सबरी नदी आ के मिले लीं के पूरा कैचमेंट एरिया के 59.7 % हिस्सा हवे, दाहिने से आ के मिले वाली नदी प्रवरा, मांजरा आ मनेर नदी बाड़ीं जे 16.1 % कैचमेंट एरिया कभर करे लीं। [[प्राणहिता नदी]] एकर सभसे महत्व वाली सहायिका हवे आ एकरे कुल थाला के 34 % एही नदी के थाला हवे। भले प्राणहिता के लंबाई बस होखे, एकरे सहायिका सभ में वर्धा, वेणगंगा आ पेनगंगा नियर बिसाल नदी बाड़ी जे लगभग पूरा बिदर्भ प्रदेश आ सतपुड़ा पहाड़ के दक्खिनी ढाल कभर करे लीं। इंद्रावती एकर दुसरी सभसे बड़हन सहायिका हवे जे ओडिशा के कालाहांडी आ नबरंगपुर के आ छत्तीसगढ़ के बस्तर एरिया ले "लाइफलाइन" कहाले। प्राणहिता आ इंद्रावती के उप-बेसिन सभ के बिस्तार के चलते इनहन के अपने आप में नदी मानल जाला। मांजरा लंबाई में एकर सभसे बड़हन (सभसे लमहर) सहायिका हवे आ एह पर निजाम सागर बान्ह बाटे। पूर्णा एगो महत्त्व वाली सहायिका हवे जे महाराष्ट्र के मराठवाड़ा नियर पानी के कमी वाला एरिया से हो के बहे ले। {{Pie chart |thumb = right |caption = '''Drainage basin of the Godavari'''<ref name="Godavari Basin">{{cite web|title=Hydrology and water resources information for India|url=http://www.nih.ernet.in/rbis/basin%20maps/godavari_about.htm|website=www.nih.ernet.in|publisher=National Institute of Hydrology, India|access-date=19 October 2015|url-status=dead|archive-url=https://web.archive.org/web/20150421200558/http://nih.ernet.in/rbis/basin%20maps/godavari_about.htm|archive-date=21 April 2015}}</ref> |label1 = Upper, middle, and lower basins of the Godavari|color1 = #FF6600 |value1 = 24.2 |label2 = [[Pranhita River|Pranhita]]|color2 = #FFFF00 |value2 = 34.87 |label3 = [[Indravati River|Indravati]]|color3 = DodgerBlue |value3 = 12.98 |label4 = [[Manjira River|Manjira]]|color4 = Green |value4 = 9.86 |label5 = [[Sabari River|Sabari]]|color5 = Gold |value5 = 6.53 |label6 = [[Purna River (tributary of Godavari)|Purna]]|color6 = Pink |value6 = 4.98 |label7 = [[Maner River|Manair]] |color7 = Black |value7 = 4.18 |label8 = [[Pravara River|Pravara]]|color8 = Gray |value8 = 2.08 }} {| class="wikitable sortable" |+ Major tributaries of the Godavari river |- ! Tributary !! Bank !! Confluence location !! Confluence elevation !! Length !! Sub-basin area |- | [[Pravara River|Pravara]] || Right || Pravara Sangam, [[Nevasa taluka|Nevasa]], [[Ahmednagar district|Ahmednagar]], [[Maharashtra]] || align="right" |{{convert|463|m|ft|abbr=on|disp=br()}} || align="right"|{{convert|208|km|mi|abbr=on|disp=br()}} || align="right"|{{convert|6,537|km2|sqmi|abbr=on|disp=br()}} |- | [[Purna River (tributary of Godavari)|Purna]] || Left || Jambulbet, [[Parbhani district|Parbhani]], [[Marathwada]], [[Maharashtra]] || align="right" |{{convert|358|m|ft|abbr=on|disp=br()}} || align="right"|{{convert|373|km|mi|abbr=on|disp=br()}} || align="right"|{{convert|15,579|km2|sqmi|abbr=on|disp=br()}} |- | [[Manjira River|Manjira]] || Right || Kandakurthi, Renjal, [[Nizamabad, Telangana]] || align="right"|{{convert|332|m|ft|abbr=on|disp=br()}} || align="right"|{{convert|724|km|mi|abbr=on|disp=br()}} || align="right"|{{convert|30,844|km2|sqmi|abbr=on|disp=br()}} |- | [[Manair River|Manair]] || Right || Arenda, [[Manthani]], [[Peddapalli district|Peddapalli]], [[Telangana]]|| align="right" |{{convert|115|m|ft|abbr=on|disp=br()}} || align="right"|{{convert|225|km|mi|abbr=on|disp=br()}} || align="right"|{{convert|13,106|km2|sqmi|abbr=on|disp=br()}} |- | [[Pranhita]] || Left || Kaleshwaram, [[Mahadevpur]], [[Jayashankar Bhupalpally]], [[Telangana]]|| align="right" |{{convert|99|m|ft|abbr=on|disp=br()}} || align="right"|{{convert|113|km|mi|abbr=on|disp=br()}} || align="right"|{{convert|109,078|km2|sqmi|abbr=on|disp=br()}} |- | [[Indravati]] || Left || Somnoor Sangam, [[Sironcha]], [[Gadchiroli district|Gadchiroli]], [[Maharashtra]] || align="right" |{{convert|82|m|ft|abbr=on|disp=br()}} || align="right"|{{convert|535|km|mi|abbr=on|disp=br()}} || align="right"|{{convert|41,655|km2|sqmi|abbr=on|disp=br()}} |- | [[Sabari River|Sabari]] || Left ||Kunawaram, [[East Godavari]], [[Andhra Pradesh]] || align="right" |{{convert|25|m|ft|abbr=on|disp=br()}} || align="right"|{{convert|418|km|mi|abbr=on|disp=br()}} || align="right"|{{convert|20,427|km2|sqmi|abbr=on|disp=br()}} |} Other than these 7 principal ones, it has many smaller but significant ones draining into it. Indravati river floodwaters overflow into the Jouranala which is part of Sabari basin. A barrage at {{Coord|19|7|19|N|82|14|9|E|name=Jouranala barrage}} is constructed across the Indravati river to divert Indravati water in to Sabari river for enhanced hydropower generation. == बान्ह == प्रणहिता सहायक नदी के संगम तक ले मुख्य गोदावरी नदी के पूरा तरीका से बांध कइल गइल बा जेह से कि उपलब्ध पानी के सिंचाई खातिर इस्तेमाल कइल जा सके। हालाँकि, एकर मुख्य सहायक नदी प्रन्हिता, इंद्रवती आ शबरी जे बेसिन के निचला हिस्सा में जुड़ल बाड़ी, मुख्य गोदावरी के तुलना में तीन गुना ढेर पानी ले जालीं। साल 2015 में आंध्र प्रदेश में स्थित प्रकाशम बैराज में पानी के उपलब्धता बढ़ावे खातिर पट्टीसीमा लिफ्ट योजना के मदद से पोलावरम दाहिना किनारे के नहर के चालू क के पानी के अधिशेष गोदावरी नदी के जल घाटा वाला कृष्णा नदी से जोड़ल गइल बा। गोदावरी नदी बेसिन में भारत के कौनों अउरी नदी बेसिन के तुलना में ढेर बाँध सभ के निर्माण होला। नदी बेसिन में स्थित कुछ बांध निम्नलिखित बाड़ें: * गंगापुर बाँध : ई एगो बड़हन मिट्टी भरल बाँध हवे जेह में सकल पानी के भंडारण 215.88 मिलियन घन मीटर बाटे, आ ई नासिक शहर से 10 किमी (6.2 मील) ऊपर के ओर स्थित बा। गंगापुर बंद सागर के नाँव से जानल जाए वाला ई जलाशय नासिक शहर के पीये के पानी उपलब्ध करावे ला आ एकलाहरे में नीचे के ओर स्थित थर्मल पावर स्टेशन के भी पानी के आपूर्ति करे ला। * जयकवाड़ी बांध : पैठन के लगे स्थित इ भारत के सबसे बड़ माटी के बांध में से एक बा। ई बाँध किनारे के किनारे बाढ़ के दोहरी समस्या के समाधान खातिर बनावल गइल रहे, मानसून के महीना में, आ सूखा के, साल के बाकी हिस्सा में, मराठवाड़ा इलाका में। दू गो 'बाँया' आ 'दाहिना' नहर से नंदेद जिला तक के उपजाऊ जमीन के सिंचाई होला। ई बाँध औरंगाबाद आ जलना, महाराष्ट्र के औद्योगिक बिकास में योगदान देले बा।[29] परभनी, नंदेद आ बीड जिला में सिंचाई के संभावना के अउरी बढ़ावे खातिर मजलगांव बांध के निर्माण भी जयकवाड़ी स्टेज 2 के तहत कइल जाला। * विष्णुपुरी बैराज: एशिया के सभसे बड़ लिफ्ट सिंचाई परियोजना, विष्णुपुरी प्रकाश[30] के निर्माण नदी पर नांदेद शहर से 5 किमी (3.1 मील) के दूरी पर कइल गइल बा। * गोदावरी नदी बेसिन के बाहर प्रवरा सहायक नदी के पानी के एगो पश्चिम बहत नदी में मोड़ के जलविद्युत उत्पादन खातिर घाटघर बांध बनावल गइल जवन अरब सागर से जुड़ल बा। * गोदावरी नदी जलग्रहण क्षेत्र के कुछ हिस्सा के मिला के पच्छिम में बहत वैतरना नदी के पार ऊपरी वैतरना जलाशय बनावल गइल। एह जलाशय में जब्त गोदावरी पानी के जलविद्युत पैदा कइला का बाद मुंबई शहर के पेयजल सप्लाई खातिर नदी बेसिन का बाहर डायवर्ट कर दिहल जाला. * श्रीराम सागर बांध : आदिलाबाद आ निजामाबाद जिला के सीमा पर गोदावरी नदी पर ई एगो अउरी बहुउद्देशीय परियोजना बा। ई निजामाबाद से 60 किलोमीटर दूर पोचाम्पड शहर के लगे बा। एकरा के द हिंदू द्वारा "तेलंगाना के एगो बड़हन हिस्सा खातिर जीवन रेखा" बतावल गइल बा। ई करीमनगर, वारंगल, आदिलाबाद, नलगोंडा, आ खम्माम जिला सभ में सिंचाई के जरूरत के पूरा करे ला आ बिजली भी पैदा करे ला। * सर आर्थर कॉटन बैराज के निर्माण सर आर्थर कॉटन द्वारा 1852 में कइल गइल।1987 के बाढ़ में एकरा के नुकसान भइल, आ एकरे तुरंत बाद बैराज सह रोडवे के रूप में फिर से बनावल गइल आ इनके नाँव पर एकर नाँव रखल गइल। ई सड़क मार्ग पूर्वी गोदावरी के राजामुंद्री आ पश्चिम गोदावरी के विजयेश्वरम के जोड़त बा। एह बैराज के सिंचाई नहर सभ भी राष्ट्रीय जलमार्ग 4 के हिस्सा हवें। == संदर्भ == {{Reflist|33em}} == बाहरी कड़ी == {{commons category|Godavari River}} * [https://www.wisdomlib.org/hinduism/book/gautami-mahatmya Gautami Mahatmya (fourth book of the Brahma-purana)] English translation by G. P. Bhatt, 1955 (includes glossary) * [http://www.sakti.in/godavaribasin/images/Godavari-sub-basins1.gif Godavari basin] {{Webarchive|url=https://web.archive.org/web/20210413202609/http://www.sakti.in/godavaribasin/images/Godavari-sub-basins1.gif |date=2021-04-13 }} * [http://www.riversnetwork.org/rbo/index.php/river-blogs/south-asia/item/3985 Rivers Network: Godavari watersheds webmap] {{Webarchive|url=https://web.archive.org/web/20190928173430/http://www.riversnetwork.org/rbo/index.php/river-blogs/south-asia/item/3985 |date=28 September 2019 }} * {{cite EB1911 |wstitle=Godavari (river) |display=Godavari |volume=12 |page=170 |short=1}} * [https://web.archive.org/web/20040823082845/http://www.nashikdiary.com/ Nashik City] * [http://adsabs.harvard.edu/abs/2001AGUSM...V22A07S Contrasting Behavior of Osmium in the Godavari River Estuary, India, 2001] * [http://ams.allenpress.com/perlserv/?request=get-abstract&doi=10.1175%2F1520-0442(1998)011%3C0768:IVOMRI%3E2.0.CO%3B2 Variations of Monsoon Rainfall in Godavari River Basin]{{Dead link|date=May 2024 |bot=InternetArchiveBot |fix-attempted=yes }} * [http://www.irfca.org/gallery/GeneralScenes/Godavari/ irfca.org] {{Waters of South Asia}} [[श्रेणी:भारत के नदी]] [[श्रेणी:महाराष्ट्र के भूगोल]] [[श्रेणी:तेलंगाना के भूगोल]] j82kw5z5vslvec7vvltu05o63vxojpc गुवाहाटी 0 9325 803324 766109 2026-07-29T16:20:57Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803324 wikitext text/x-wiki {{Infobox settlement | name = गुवाहाटी | native_name = | other_name = गौहाटी(औपनिवेशिक)<br> प्रगज्योतिशपुरा(प्राचीन) | settlement_type = [[महानगर]] | image_skyline = {{Photomontage | photo1a = | photo2a = MG 2907-1 Guwahati Club.jpg | photo2b = Guwahati Planetarium.jpg | photo3a = Guwahati Railway Station at Night.jpg | photo3b = Kamakhya Temple, Guwahati.jpg | space = 1 | size = 280 | color = white | color_border = white }} | image_alt = Guwahati collage.jpg | image_caption = A view of Guwahati city | image_seal = | seal_alt = | nickname = Gateway to Northeast India,<ref>{{cite web|url=https://www.theguardian.com/cities/2015/sep/28/insider-guide-guwahati-gateway-india-northeast |title=An insider's guide to Guwahati: more than just a gateway to India’s northeast| publisher= The Guardian|date= |accessdate=5 अक्टूबर 2015}}</ref> City of Temples, Light of the East<ref>{{cite web |url=http://www.kamrupmetro.nic.in/indexmain.asp |title=Kamrup Metro District |publisher=Kamrup(M) District Administration |accessdate=5 अक्टूबर 2015 |url-status=dead |archiveurl=https://web.archive.org/web/20150911190123/http://kamrupmetro.nic.in/indexmain.asp |archivedate=11 September 2015 |df=dmy-all }}</ref> | image_map = | map_alt = | map_caption = | pushpin_map = India Assam#India | pushpin_label_position = | pushpin_map_alt = | pushpin_map_caption = Location of Guwahati in Assam | coordinates = {{coord|26|11|N|91|44|E|display=inline,title}} | subdivision_type = देश | subdivision_name = {{Flag|भारत}} | subdivision_type1 = राज्य | subdivision_name1 = [[असम]] | subdivision_type2 = क्षेत्र | subdivision_name2 = निचला असम | subdivision_type3 = जिला | subdivision_name3 = [[Kamrup Metropolitan district]] | established_title = <!-- Established --> | established_date = | founder = | named_for = | government_type = Republic | governing_body = [[Guwahati Municipal Corporation]] | leader_title1 = [[Mayor]] | leader_name1 = Mrigen Sarania ([[Bharatiya Janata Party|BJP]]) | leader_title2 = Deputy Commissioner | leader_name2 = Virendra Mittal, IAS<ref>{{cite web|url=http://cm.assam.gov.in/dc.php|title=Chief Minister of Assam - Deputy Commissioners|access-date=2018-09-10|archive-date=2018-05-10|archive-url=https://web.archive.org/web/20180510122644/http://cm.assam.gov.in/dc.php|url-status=dead}}</ref> | leader_title3 = Police Commissioner | leader_name3 = Sri Hiren Chandra Nath, IPS<ref>{{cite web |url=http://guwahaticitypolice.gov.in/contact_us/senior_officers.php |title=Police Commissionerate Guwahati |publisher=Guwahati City Police |date= |accessdate=15 September 2015 |archive-date=6 September 2015 |archive-url=https://web.archive.org/web/20150906155742/http://guwahaticitypolice.gov.in/contact_us/senior_officers.php |url-status=dead }}</ref> | area_footnotes = <ref name="The Telegraph">{{cite web|url=http://www.telegraphindia.com/1150101/jsp/northeast/story_6181.jsp#.VffzEfmqpBc |title= Magisterial powers for Guwahati top cop |publisher=The Telegraph |date= |accessdate=15 September 2015}}</ref> | area_total_km2 = 328 | area_total_sq_mi = 128 | elevation_footnotes = | elevation_m = 50-680 <!-- Google Earth elevation scale, from Pandu Ghat to Ahom Gaon near ISBT. Do not modify. Average elevation is a nonsensical measure for a hilly city and thus altitude extremeties carry valuable meaning. Similar is done with most hilly or mountainous cities on the planet. From Tehran to Los Angeles to Zurich to even Delhi. --> | elevation_ft = 164-2231 | population_footnotes = <ref>{{cite web|url=http://www.census2011.co.in/census/city/191-guwahati.html |title= Guwahati City Census| publisher=censusindia.gov.in | date= |accessdate=15 September 2015}}</ref> | population_total = 957352 | population_as_of = 2011 | population_rank = [[List of most populous cities in India|48th]] | population_density_km2 = auto | population_metro_footnotes = | population_metro = 962,334 ([[urban agglomeration|UA]]) | population_demonym = Guwahatian | timezone1 = [[भारतीय मानक समय|आइएसटी]] | utc_offset1 = +5:30 | postal_code_type = [[Postal Index Number|PIN]] | postal_code = 781 XXX | area_code_type = Telephone code | area_code = +91 - (0) 361 - XX XX XXX | iso_code = [[ISO 3166-2:IN|IN-AS]] | registration_plate = AS-01 (Kamrup Metro) / AS-25 (Kamrup Rural and Dispur) | blank1_name_sec1 = [[Human Development Index|HDI]] | blank1_info_sec1 = {{increase}} 0.725 {{color|#fc5| high}}<ref>{{cite web|url=http://www.okd.in/downloads/assam_hdr_30sep2016.pdf|title=आर्काइव कॉपी के बा|access-date=2018-09-10|archive-date=2017-03-22|archive-url=https://web.archive.org/web/20170322195446/http://okd.in/downloads/assam_hdr_30sep2016.pdf|url-status=dead}}</ref> | blank2_name_sec1 = City animal | blank2_info_sec1 = [[South Asian river dolphin|Gangetic river Dolphin]]<ref>{{cite web|url=http://m.timesofindia.com/home/environment/flora-fauna/Gangetic-river-dolphin-to-be-city-animal-of-Guwahati/articleshow/52623206.cms|title=Gangetic river dolphin to be city animal of Guwahati|work=[[दि टाइम्स ऑफ इंडिया]]|accessdate=14 जून 2017|date=6 जून 2017}}</ref> | blank1_name_sec2 = {{nowrap|[[Official language]]}} | blank1_info_sec2 = [[Assamese language|Assamese]] is city's official language used in governmental and conversation purpose , other languages spoken across city includes -: [[Bodo language|Bodo]], [[Bangla language|Bengali]], [[Hindi language|Hindi]], [[Mishing language|Mishing]], [[Rabha language|Rabha]], [[Kachari language|Kachari]], [[Meitei language|Meitei]], and [[Punjabi language|Punjabi]]. | blank2_name_sec2 = [[Literacy in India|Literacy]] | blank2_info_sec2 = {{increase}} 91.47% {{color|#fc5|high}}<ref>{{cite web|url=http://www.census2011.co.in/census/city/191-guwahati.html |title=Guwahati City Census 2011 data | publisher=Census2011.co.in |date= |accessdate=2014-09-22}}</ref> | blank3_name_sec2 = [[Climate of India|Climate]] | blank3_info_sec2 = [[Climatic regions of India|Cwa]] {{small|([[Köppen climate classification|Köppen]])}} | website = {{URL|www.gmcportal.in/}} }} '''गुवाहाटी''' चाहे '''गौहाटी''' पूर्वोत्तर भारत की [[आसाम]] में सभसे बड़ शहर आ मेट्रो एरिया हवे आ ई पूर्वोत्तर भारत के भी सभसे बड़ शहर हवे। असम के राजधानी [[दिसपुर]] गुवाहाटी के भीतर स्थित सर्किट सिटी क्षेत्र में बा आ असम सरकार के मुख्यालय ह। पहाड़ी सभ के साथ एगो प्रमुख नदी किनारे के बंदरगाह शहर, आ भारत के सभसे तेजी से बढ़त[12] शहर सभ में से एक, गुवाहाटी [[ब्रह्मपुत्र नदी|ब्रह्मपुत्र]] के दक्खिनी किनारे पर स्थित बा। एकरा के ''पूर्वोत्तर भारत के प्रवेश द्वार'' कहल जाला। प्राचीन शहर प्रागज्योतिषपुरा आ दुर्जय (उत्तर गुवाहाटी) प्राचीन राज्य कामरूप के राजधानी रहलें। कई गो प्राचीन हिंदू मंदिर जइसे कि [[कामाख्या मंदिर]], उग्रतारा मंदिर, बसिष्ठ मंदिर, दोल गोविन्द मंदिर, उमानन्द मंदिर, नवग्रह मंदिर, सुखरेश्वर मंदिर, रुद्रेश्वर मंदिर, मणिकर्णेश्वर मंदिर, अश्वक्लांत मंदिर, दीर्घेश्वरी मंदिर, अश्वक्रांता मंदिर, लंकेश्वर मंदिर, भुवनेश्वरी मंदिर, शुभ .गणेश मंदिर, श्री पंचायतन मंदिर, नूनमाटी, आ अइसने कई गो मंदिर, शहर में बाड़ें जवना से एकरा के "मंदिर के शहर" के उपाधि दिहल गइल बा. गुवाहाटी ब्रह्मपुत्र नदी के किनारे आ [[शिलॉंग पठार|शिलांग पठार]] के तलहटी के बीच में पड़े ला आ पच्छिम में एलजीबी अंतर्राष्ट्रीय हवाई अड्डा आ पूरब में नरेंगी शहर बा। ब्रह्मपुत्र के उत्तरी तट तक के उत्तर गुवाहाटी इलाका के धीरे-धीरे शहर के सीमा में शामिल कइल जा रहल बा। प्रख्यात मदन कामदेव गुवाहाटी से 30 किलोमीटर (19 मील) दूर स्थित बा। गुवाहाटी नगर निगम, शहर के स्थानीय सरकार, 216 वर्ग किलोमीटर (83 वर्ग मील) के क्षेत्रफल के प्रबंधन करे ले, जबकि गुवाहाटी महानगर बिकास प्राधिकरण (GMDA) ग्रेटर गुवाहाटी महानगर क्षेत्र के योजना आ बिकास निकाय हवे। गुवाहाटी पूर्वोत्तर भारत के सभसे बड़ शहर बा। गुवाहाटी इलाका में बिबिध वन्यजीव सभ पावल जालें जेह में एशियाई हाथी, अजगर, बाघ, गैंडा, गौर, प्राइमेट प्रजाति सभ, आ बिलुप्त होखे वाला चिरई सभ नियर दुर्लभ जानवर सामिल बाड़ें। {{clear}} ==संदर्भ== {{Reflist|33em}} {{भारत-भू-आधार}} [[श्रेणी:भारत के शहर आ कस्बा]] [[श्रेणी:पूर्बोत्तर भारत]] ta5cxpzon267m8pvoukjxw7j587jmuz उत्तर प्रदेश के नाच 0 9479 803358 795866 2026-07-29T16:23:24Z SM7Bot 7808 /* ख़्याल नृत्य */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803358 wikitext text/x-wiki {{अनुवाद}} '''[[लोकनाच]]''' में [[उत्तर प्रदेश]] के मय अंचल के आपन विशिष्ट चिन्हासी बा। समृद्ध विरासत के विविधता के संईतल ई आंगिक कलारूप [[लोक संस्कृति]] के प्रमुख वाहक बङुवे। == ख़्याल नृत्य == '''ख़्याल नृत्य''' पुत्र जन्मोत्सव पे [[बुंदेलखण्ड]] में कइल जाला। एह में रंगीन कागज आ बांस के मदद से एगो [[मंदिर]] बनावल जाला आ ओकरा बाद ओकरा के माथा पे रख के नाच कइल जाला।<ref>{{Cite web |url=http://www.indiaheritage.org/perform/dance/dup.htm |title=आर्काइव कॉपी के बा |access-date=2014-06-25 |archive-date=2013-07-04 |archive-url=https://web.archive.org/web/20130704151826/http://www.indiaheritage.org/perform/dance/dup.htm |url-status=dead }}</ref> == रास नृत्य == '''रास नृत्य''' [[ब्रज]] में [[रासलीला]] के दौरान किया जाता है। [[रासक दण्ड नृत्य]] भी इसी क्षेत्र का एक आकर्षक नृत्य है। <ref>{{Cite web |url=http://www.dancesofindia.co.in/folk-dances-india/uttar-pradesh/ |title=आर्काइव कॉपी के बा |access-date=2014-06-25 |archive-date=2012-06-15 |archive-url=https://web.archive.org/web/20120615003259/http://www.dancesofindia.co.in/folk-dances-india/uttar-pradesh/ |url-status=dead }}</ref> == झूला नृत्य == '''झूला नृत्य''' भी ब्रज क्षेत्र का नृत्य है, जिसका आयोजन [[श्रावण]] मास में किया जाता है। इस नृत्य को इस क्षेत्र के मंदिरों में बड़े उल्लास के साथ किया जाता है। == मयूर नृत्य == '''मयूर नृत्य''' भी ब्रज क्षेत्र का ही नृत्य है। इसमें नर्तक [[मोर]] के पंख से बने विशेष वस्त्र धारण करते हैं। == धोबिया नृत्य == '''धोबिया नृत्य''' [[पूर्वांचल]] में प्रचलित है। यह नृत्य [[धोबी]] समुदाय द्वारा किया जाता है। इसके माध्यम से धोबी एवं गदहे के मध्य आजीविका संबंधों का भावप्रवण निरूपण किया जाता है। == चरकुला नृत्य == '''चरकुला नृत्य''' ब्रज क्षेत्रवासियों द्वारा किया जाता है। इस [[घड़ा]] नृत्य में [[बैलगाड़ी]] अथवा [[रथ]] के पहिये पर कई घड़े रखे जाते हैं फिर उन्हें सिर पर रखकर नृत्य किया जाता है। == कठफोड़वा नृत्य == '''कठघोड़वा नृत्य''' पूर्वांचल और बिहार-झारखंड के मगध क्षेत्र में माँगलिक अवसरों पर किया जाता है। इसमें एक नर्तक अन्य नर्तकों के घेरे के अंदर कृत्रिम घोड़ी पर बैठकर नृत्य करता है। == जोगिनी नृत्य == '''जोगिनी नृत्य''' विशेषकर [[रामनवमी]] के अवसर पर किया जाता है। इसके अंतर्गत साधु या कोई अन्य पुरुष महिला का रूप धारण करके नृत्य करते हैं। == धींवर नृत्य == '''धींवर नृत्य''' अनेक शुभ अवसरों पर विशेषकर [[कहार]] जाति के लोगों द्वारा आयोजित किया जाता है। == शौरा नृत्य == '''शौरा या सैरा नृत्य''' [[बुंदेलखण्ड]] के [[कृषक]] अपनी फसलों को काटते समय हर्ष प्रकट करने के उद्देश्य से करते हैं। == कर्मा नृत्य == '''कर्मा व शीला नृत्य''' [[सोनभद्र]] और [[मिर्जापुर]] के [[खखार]] आदिवासी समूह द्वारा आयोजित किया जाता है। == पासी नृत्य == '''पासी नृत्य''' [[पासी]] जाति के लोगों द्वारा सात अलग अलग मुद्राओं की एक गति तथा एक ही लय में [[युद्ध]] की भाँति किया जाता है। == घोड़ा नृत्य == '''घोड़ा नृत्य''' [[बुंदेलखण्ड]] में माँगलिक अवसरों पर बाजों की धुन पर घोड़ों द्वारा करवाया जाता है। == धुरिया नृत्य == '''धुरिया नृत्य''' को [[बुंदेलखण्ड]] के प्रजापति ([[कुम्हार]]) स्त्री वेश धारण करके करते हैं। == छोलिया नृत्य == '''छोलिया नृत्य''' [[राजपूत]] जाति के लोगों द्वारा विवाहोत्सव पर किया जाता है। इसे करते समय नर्तकों के एक हाथ में [[तलवार]] तथा दूसरे हाथ में [[ढाल]] होती है। == छपेली नृत्य == '''छपेली नृत्य''' एक हाथ में [[रुमाल]] तथा दूसरे हाथ में [[दर्पण]] लेकर किया जाता है। इस के माध्यम से नर्तक आध्यात्मिक समुन्नति की कामना करते हैं। == नटवरी नृत्य == '''नटवरी नृत्य''' [[पूर्वांचल]] क्षेत्र के [[अहीर|अहीरों]] द्वारा किया जाता है। यह नृत्य गीत व [[नक्कारा|नक्कारे]] के सुरों पर किया जाता है। == देवी नृत्य == '''देवी नृत्य''' अधिकांशतः [[बुंदेलखण्ड]] में ही प्रचलित है। इस लोक नृत्य में एक नर्तक देवी का स्वरूप धारण कर अन्य नर्तकों के सामने खड़ा रहता है तथा उसके सम्मुख शेष सभी नर्तक नृत्य करते हैं। == राई नृत्य == '''राई नृत्य''' [[बुंदेलखण्ड]] की महिलाओं द्वारा किया जाता है। यहाँ की महिलाएँ इस नृत्य को विशेषतः [[श्रीकृष्ण जन्माष्टमी]] के अवसर पर करती हैं। इसको मयूर की भाँति किया जाता है, इसीलिेए यह '''मयूर नृत्य''' भी कहलाता है। == दीप नृत्य == [[बुंदेलखण्ड]] के अहीरों द्वारा अनेकानेक दीपकों को प्रज्ज्वलित कर किसी घड़े, कलश अथवा थाल में रखकर प्रज्ज्वलित दीपकों को सिर पर रखकर '''दीप नृत्य''' किया जाता है। == पाई डण्डा नृत्य == '''पाई डण्डा नृत्य''' [[गुजरात]] के [[डाण्डिया नृत्य]] के समान है जो कि [[बुंदेलखण्ड]] के अहीरों द्वारा किया जाता है। == कार्तिक नृत्य == '''कार्तिक नृत्य''' [[बुंदेलखण्ड]] क्षेत्र में [[कार्तिक माह]] में नर्तकों द्वारा [[श्रीकृष्ण]] तथा गोपियों का रूप धरकर किया जाता है। == कलाबाज नृत्य == '''कलाबाज नृत्य''' [[अवध]] क्षेत्र के नर्तकों द्वारा किया जाता है। इस नृत्य में नर्तक मोरबाजा लेकर कच्ची घोड़ी पर बैठ कर नृत्य करते हैं। == मोरपंख व दीवारी नृत्य == [[ब्रज]] की [[लट्ठमार होली]] की तरह [[बुंदेलखण्ड]] विशेषकर [[चित्रकूट]] में [[दीपावली के]] दिन ढोल-नगाड़े की तान पर आकर्षक वेशभूषा के साथ मोरपंखधारी लठैत एक दूसरे पर ताबड़तोड़ वार करते हुए '''मोरपंख व दीवारी नृ्त्य''' व खेल का प्रदर्शन करते हैं। == ठडिया नृत्य == [[सोनभद्र]] व पड़ोसी जिलों में संतान की कामना पूरी होने पर '''ठडिया नृ्त्य''' का आयोजन [[सरस्वती]] के चरणों समर्पित होकर किया किया जाता है। == चौलर नृत्य == [[मिर्जापुर]] और [[सोनभद्र]] आदि जिलों में '''चौलर नृत्य''' अच्छी वर्षा तथा अच्छी फसल की कामना पूर्ति हेतु किया जाता है। == ढेढिया नृत्य == '''ढेढिया नृत्य''' का प्रचलन [[द्वाबा]] क्षेत्र में है। राम के लंका विजय के पश्चात वापस आने पर स्वागत में किया जाता है। इसमें सिरपर छिद्रयुक्त मिट्टी के बर्तन में दीपक रखकर किया जाता है। == ढरकहरी नृत्य == [[सोनभद्र]] की जनजातियों द्वारा '''ढरकहरी नृत्य''' का आयोजन किया जाता है। == संदर्भ == {{reflist}} == बाहरी कड़ियाँ == * [http://www.indiaheritage.org/perform/dance/dup.htm'''भारत की विरासत'''] {{Webarchive|url=https://web.archive.org/web/20130704151826/http://www.indiaheritage.org/perform/dance/dup.htm |date=2013-07-04 }} * [http://www.dancesofindia.co.in/folk-dances-india/uttar-pradesh/'''भारत के लोकनृत्य'''] {{Webarchive|url=https://web.archive.org/web/20120615003259/http://www.dancesofindia.co.in/folk-dances-india/uttar-pradesh/ |date=2012-06-15 }} * [http://www.indiatourisminfo.net/uttar-pradesh/dance-in-uttar-pradesh/ "उत्तरप्रदेश के सभी लोक नृत्य की सूचि"] {{Webarchive|url=https://web.archive.org/web/20130402174037/http://www.indiatourisminfo.net/uttar-pradesh/dance-in-uttar-pradesh/ |date=2013-04-02 }} [[श्रेणी:उत्तर प्रदेश क नाच]] [[श्रेणी:उत्तर प्रदेश|नाच]] [[श्रेणी:लोक नाच]] 8sh7nu3lsjcn0a1t13xsll8xzz6dwjf खिचड़ी 0 9627 803319 756551 2026-07-29T16:20:36Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803319 wikitext text/x-wiki {{for2|एही नाँव के तिहुआर|[[खिचड़ी (तिहुआर)]] आ [[मकर संक्रांति]]}} {{Infobox prepared food | name = खिचड़ी | image = [[File:Khichuri-edit.jpg#file|250px]] | caption = खिचड़ी | country = भारतीय उपमहाद्वीप | region = [[भारत]], [[पाकिस्तान]], [[नेपाल]] | creator = | course = | served = | main_ingredient = चावल, दाल, चाट मसाला | variations = | calories = | other = }} '''खिचड़ी''' एगो भोजन क चीज हवे जेवन दाल आ चावल (चाउर) से बनावल जाला| ''खिचड़ी'' शब्द क उत्पत्ति [[संस्कृत]] की खिच्चा शब्द से भइल बा, जेकर मतलब होला, चावल और फली क एगो पकवान।<ref>{{cite web|title=मास्टर चेफ (यू.एस. सीज़न 6)|url=https://en.wikipedia.org/wiki/MasterChef_(U.S._season_6)}}</ref> ==इतिहास== सेलेकस क ग्रीक राजदूत उल्लेख कई न की दाल की साथ चावल दक्षिण एशिया में [[भारत]] की लोगन की बीच बहुत लोकप्रिय बा। मोरक्को क यात्री, इब्न बट्टुता 1350 की आसपास सिरका में रहले की दौरान, चावल और मूंग से बनल एगो बानगी के किशरी की रूप में उल्लेख कइले बा। अफानासीय निकितिन, एगो रूसी साहसिक जवन 15 वीं सदी में दक्षिण एशिया क यात्रा कइले रहे, ओकरी लेखन में खिचड़ी क वर्णन मिलेला। खिचड़ी मुग़ल साम्राज्य में बहुत लोकप्रिय रहे, खासकर जहांगीर की समय में। मुगल सम्राट [[अकबर]] की वज़ीर, अबू-फजल इब्न मुबारक द्वारा लिखित 16 वीं शताब्दी की दस्तावेज़ आईन-ए-अकबरी में, खिचड़ी की नुस्खा क उल्लेख बा, जवन की सात प्रकार क बा। अकबर, बीरबल और खिचड़ी क एगो अनोखा कहानीयो क जिक्र मिलेला। ==क्षेत्रीय रूपांतर== [[पाकिस्तान]], [[नेपाल]] और भारत में खिचड़ी एगो बहुत लोकप्रिय पकवान ह। इ पकवान भारत, [[गुजरात]], [[बंगाल]], [[बिहार]], [[झारखंड]], उत्तर प्रदेश, ओडिशा और [[महाराष्ट्र]] जैसन कईगो राज्यन में व्यापक रूप से तैयार कइल जाला। फूलगोभी, आलू और हरा मटर जैसे सब्जियां सामान्य तौर पर एमें मिला दिहल जाला। तटीय महाराष्ट्र में एकर एगो लोकप्रिय प्रकार झींगा से बनेला। एकही बर्तन में बहुत आसानी से बनवले की सुविधा की चलते खिचड़ी एगो पसंदीदा कैम्प फायर बानगी ह। अन्य कई क्षेत्र में, खिचड़ी के आमतौर प कढ़ी की साथ परोसल जाला, और अक्सर साथ पापड़ भी रहेला। ==संदर्भ== <references/> {{भोजन-आधार}} [[श्रेणी:भारतीय खाना]] ap1eehd9uohkxk6gfhdhz6oo5hebqni भारतीय गिद्ध 0 10461 803357 780258 2026-07-29T16:23:18Z SM7Bot 7808 /* आबादी में कमी */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803357 wikitext text/x-wiki {{speciesbox | status = CR | status_system = IUCN3.1 | status_ref = <ref name=iucn>{{cite iucn |author=BirdLife International |date=2017 |title=''Gyps indicus'' |page=e.T22729731A117875047 |access-date=19 November 2021}}</ref> | image = Indian vulture (Gyps indicus) Photograph by Shantanu Kuveskar.jpg | image_caption = Indian vulture sunbathing in [[Mhasla]], [[Maharashtra]] | genus = Gyps | species = indicus | authority = ([[Giovanni Antonio Scopoli|Scopoli]], 1786)<ref>{{cite book |author=Scopoli, J. A. |year=1786–88 |title=Deliciae Flora et Fauna Insubricae Ticini. An account including new descriptions of the birds and mammals collected by Pierre Sonnerat on his voyages |location=London |publisher=C. J. Clay |pages=7–18 |chapter=Aves |chapter-url=https://archive.org/details/cu31924005326800/page/n13/mode/2up}}</ref> | range_map = GypsBengalensisMap.svg | range_map_caption = Distribution in purple }} '''भारतीय गिद्ध''' ({{Lang|en|Indian vulture}}, Gyps indicus) एगो पुरान दुनिया के गिद्ध हवे जे [[भारत]], [[पाकिस्तान]] आ [[नेपाल]] के मूल निवासी हवे। एकरा के 2002 से [[अंतर्राष्ट्रीय प्रकृति संरक्षण संघ|आईयूसीएन]] के रेड लिस्ट में गंभीर रूप से बिलुप्त होखे वाला के रूप में लिस्ट कइल गइल बाटे, काहें से कि एह में जनसंख्या में बहुत गिरावट आइल बा। भारतीय गिद्ध सभ के मौत डाइक्लोफेनाक जहर के कारण किडनी फेल होखे से भइल मानल जाला।<ref name=iucn /> एकर प्रजनन मुख्य रूप से मध्य आ प्रायद्वीपीय भारत के पहाड़ी चट्टान सभ पर होला। एकरे रेंज के उत्तरी हिस्सा में मौजूद पातर-चोंच वाला गिद्ध ''जिप्स टेनुइरोस्ट्रिस'' के एगो अलग प्रजाति मानल जाला। ==बिबरन== भारतीय गिद्ध मध्यम आकार के आ भारी-भरकम होला। एकर देह आ गुप्त पंख पीयर होला, एकर उड़ान पंख गहिराह रंग के होला। एकर पाँख चौड़ा होला आ पोंछ के पंख छोट होला। एकर माथा आ गर्दन लगभग गंजा होला, आ एकर नोक काफी लंबा होला। एकर लंबाई 81–103 सेमी (32–41 इंच) होला आ एकर पाँख के बिस्तार 1.96–2.38 मीटर (6.4–7.8 फीट) होला। मादा नर से छोट होखे लीं।<ref>{{cite book |last1=Ferguson-Lees |first1=J. |last2=Christie |first2=D. A. |name-list-style=amp |year=2001 |title=Raptors of the World |location=Boston, New York |publisher=Houghton Mifflin Harcourt |isbn=0618127623 |page=120 |chapter=Long-billed Vulture ''Gyps indicus'' |chapter-url=https://books.google.com/books?id=hlIztc05HTQC&pg=PA120}}</ref> एकर वजन 5.5–6.3 किलोग्राम (12–14 पाउंड) होला। ई यूरेशियन ग्रिफन के तुलना में छोट आ कम भारी बनल बा। ई ओह प्रजाति से अलगा होला काहें से कि एकर शरीर कम बफ आ पाँख के आवरण होला। एकरे अलावा एकरा में ग्रिफन सभ द्वारा देखावल जाए वाला सफेद रंग के मीडियन गुप्त बार के भी कमी बा।<ref>{{cite web |url=http://www.peregrinefund.org/vulture_factsheet.asp#Long-billed%20Vulture, |title=The Peregrine Fund |publisher=The Peregrine Fund |date=2010 |access-date=2011-05-31 |archive-url=https://web.archive.org/web/20110526203258/http://www.peregrinefund.org/vulture_factsheet.asp#Long-billed%20Vulture, |archive-date=2011-05-26 |url-status=dead}}</ref> == आवास आ बेहवार== भारतीय गिद्ध मुख्य रूप से दक्खिन आ मध्य भारत के चट्टान सभ पर प्रजनन करे ला, बाकी [[राजस्थान]] में घोंसला बनावे खातिर पेड़ सभ के इस्तेमाल करे खातिर जानल जाला। ई मानव निर्मित ऊँच बिल्डिंग सभ पर भी प्रजनन क सके ला, जइसे कि चतुर्भुज मंदिर। बाकी गिद्ध सभ नियर ई एगो मेहतर हवे, ज्यादातर लाश सभ से भोजन करे ला, जेकरा के ई सवाना के ऊपर आ मनुष्य के निवास के आसपास उड़ के पावे ला। ई अक्सर झुंड में जमा हो जाला। == इस्थिति आ कंजर्वेशन== ===आबादी में कमी=== भारतीय गिद्ध आ सफेद-रम्प वाला गिद्ध, जी. बेंगालेंसिस ([[बंगाल गिद्ध]]), दुनों प्रजाति सभ के ऊपर आबादी के कमी के खतरा रहल बाटे। बांग्लादेश, भारत आ नेपाल में इनहन के 99%–97% आबादी कम भइल दर्ज कइल गइल बाटे। 2000-2007 के बीच एह प्रजाति आ पतला बिल वाला गिद्ध के सालाना गिरावट के दर औसतन सोलह प्रतिशत से ढेर रहल।<ref name="BirdLife">{{ cite web |url=http://www.birdlife.org/datazone/speciesfactsheet.php?id=31029 | title = BirdLife Fact Sheet: Indian vulture | access-date = 11 August 2014 |publisher=[[BirdLife International]]}}</ref> एकर कारण पशु चिकित्सा दवाई डाइक्लोफेनाक से होखे वाला जहर के रूप में पहचानल गइल बा। डाइक्लोफेनाक एगो नॉन स्टेरॉयडल एंटी-इंफ्लेमेटरी दवाई (NSAID) ह अउरी जब इ काम करेवाला जानवर के दिहल जाला त इ जोड़ के दर्द के कम क सकता अउरी एहीसे एकरा ले जादे समय तक काम करत रह सके लें। मानल जाता कि इ दवाई गिद्ध मरे वाला मवेशी के मांस के संगे निगल लेले बाड़े, जवना के जीवन के अंतिम दिन में डाइक्लोफेनाक दिहल गइल रहे। डाइक्लोफेनाक के चलते गिद्ध के कई प्रजाति में किडनी फेल हो जाला। मार्च 2006 में भारत सरकार डाइक्लोफेनाक के पशु चिकित्सा के इस्तेमाल पर रोक लगावे के समर्थन करे के घोषणा कइलस। एगो अउरी एनएसएआईडी मेलोक्सिकैम गिद्ध खातिर हानिरहित पावल गइल बा आ ई डाइक्लोफेनाक के स्वीकार्य विकल्प साबित होखे के चाहत रहल। जब मेलोक्सिकैम के उत्पादन बढ़ जाई त उम्मीद कइल जाला कि इ डाइक्लोफेनाक नियन सस्ता होई। अगस्त 2011 ले लगभग एक साल ले पशु चिकित्सा के इस्तेमाल पर रोक लगावे से पूरा भारत में डाइक्लोफेनाक के इस्तेमाल से रोकल ना गइल।<ref name="Express">{{cite news |url = http://archive.indianexpress.com/news/banned-diclofenac-still-kills-vultures/828303/ | title = Banned diclofenac still kills vultures |first = Raakhi | last = Jagga | date = 7 August 2011 | newspaper = Express India | access-date = 11 August 2014}}</ref> पूरा प्रायदीपीय भारत में, कर्नाटक आ तमिलनाडु में, खासतौर पर बैंगलोर के आसपास के गाँव सभ में, कम संख्या में चिरई सभ के प्रजनन भइल बा।<ref name="Hindu">{{cite news|url=http://www.thehindu.com/news/national/tamil-nadu/longbilled-vultures-sighted-after-40-years/article5322851.ece|title=Long-billed Vulture sighted after 40 years|newspaper=The Hindu|date=7 November 2013 |first = P. |last = Oppili | access-date = 11 August 2014}}</ref> भारतीय गिद्ध के गिरावट से पर्यावरण के संरक्षण प बहुत असर पड़ल बा। सभ लाश सभ के हटा के गिद्ध सभ प्रदूषण के कम करे, बेमारी के फइलल आ अवांछित स्तनधारी सभ के मेहतर सभ के दबावे में मदद कइले रहलें।<ref>{{Cite journal|last1=Prakash|first1=V. |last2=Bishwakarma|first2=M. C. |last3=Chaudhary |first3=A. |last4=Cuthbert |first4=R. |last5=Dave |first5=R. |last6=Kulkarni |first6=M. |last7=Kumar |first7=S. |last8=Paudel |first8=K. |last9=Ranade |first9=S. |name-list-style=amp |date=2012 |title=The Population Decline of Gyps Vultures in India and Nepal Has Slowed since Veterinary Use of Diclofenac was Banned |journal=PLOS ONE |volume=7 |issue=11 |pages=e49118 |doi=10.1371/journal.pone.0049118 |pmc=3492300 |pmid=23145090 |bibcode=2012PLoSO...749118P|doi-access=free }}</ref> इनहन के अनुपस्थिति में जंगली कुकुरन आ चूहा सभ के आबादी के साथे-साथ इनहन के जूनोटिक बेमारी सभ में भी बहुत बढ़ती भइल बा।<ref>{{cite journal |last1=Prakash |first1=V. |last2=Pain |first2=D.J. |last3=Cunningham |first3=A.A. |last4=Donald |first4=P.F. |last5=Prakash |first5=N. |last6=Verma |first6=A. |last7=Gargi |first7=R. |last8=Sivakumar |first8=S. |last9=Rahmani |first9=A.R. |name-list-style=amp |title=Catastrophic collapse of Indian white-backed ''Gyps bengalensis'' and long-billed ''Gyps indicus'' vulture populations |journal=Biological Conservation |date=2003 |volume=109 |issue=3 |pages=381–390 |doi=10.1016/S0006-3207(02)00164-7}}</ref> ===कैप्टिव-ब्रीडिंग === [[File:Vultures in the nest, Orchha, MP, India edit.jpg|thumb|Vultures in a nest in [[Orchha]]]] भारतीय गिद्ध के कई गो प्रजाति खातिर कैप्टिव-ब्रिडिंग कार्यक्रम (बंधक राखि के प्रजनन) शुरू हो गइल बा। गिद्ध सभ लंबा उमिर के होलें आ प्रजनन में धीमा होलें, एह से एह कार्यक्रम सभ में दशक भर के समय लागे के उमेद बा। गिद्ध लगभग पाँच साल के उमिर में प्रजनन के उमिर में पहुँच जालें। उम्मीद बा कि जब पर्यावरण में डाइक्लोफेनाक से साफ हो जाई त कैद में पोसल चिरई के जंगल में छोड़ दिहल जाई। साल 2014 के सुरुआत में "सेविंग एशिया के गिद्ध" नाँव से गिद्ध सभ के बिलुप्त होखे से बचावे के कार्यक्रम घोषणा कइलस कि एकरा उमेद बा कि 2016 ले कैप्टिव-बर्ड चिरई सभ के जंगल में छोड़ल शुरू क दिहल जाई।<ref>{{cite news|last=Kinver|first=M. |title=Project targets 2016 for Asian vultures release|url=https://www.bbc.co.uk/news/science-environment-25963100|access-date=2 February 2014 |newspaper=BBC News|date=31 Jan 2014}}</ref> एशिया के पहिला गिद्ध के पुनर्परिचय कार्यक्रम के हिस्सा के रूप में जून, 2016 में जटायू संरक्षण प्रजनन केंद्र, पिंजौर से दू गो कैप्टिव हिमालयन ग्रिफन के रिहा कइल गइल। ==इहो देखल जाय== *[[बंगाल गिद्ध]] *[[सफ़ेद गिद्ध]] == संदर्भ == {{Reflist|33em}} == बाहरी कड़ी == {{Commons category|Gyps indicus}} {{Wikispecies|Gyps indicus}} * [https://web.archive.org/web/20150722223425/http://www.vulturerescue.org/ Conserving Asia's critically endangered vultures] * [http://www.vulture-territory.com/billed.html Vulture Territory Facts and Characteristics: Long Billed Griffon] * [http://www.save-vultures.org/ "Saving Asia's Vultures from Extinction" Consortium] * [https://web.archive.org/web/20170107044924/http://www.snmcpn.org/vultures/indian-white-backed-vulture Conservation of Vultures in Konkan region] <!-- {{Vulture}}--> {{Taxonbar|from=Q248086}} [[श्रेणी:भारत के चिरई]] [[श्रेणी:गिद्ध]] lp64d5z3qp8bbpg3lcghlnqn8nmbj11 Module:Check for unknown parameters 828 14742 803635 778426 2025-10-09T18:33:27Z en>MSGJ 0 add option to bulk-include mapframe parameters, code by [[User:Zackmann08]] 803635 Scribunto text/plain -- This module may be used to compare the arguments passed to the parent -- with a list of arguments, returning a specified result if an argument is -- not on the list require ('strict'); local p = {} local mapframe_params = { 'mapframe', 'mapframe-area_km2', 'mapframe-area_mi2', 'mapframe-caption', 'mapframe-coord', 'mapframe-coordinates', 'mapframe-custom', 'mapframe-frame-coord', 'mapframe-frame-coordinates', 'mapframe-frame-height', 'mapframe-frame-width', 'mapframe-geomask', 'mapframe-geomask-fill', 'mapframe-geomask-fill-opacity', 'mapframe-geomask-stroke-color', 'mapframe-geomask-stroke-colour', 'mapframe-geomask-stroke-width', 'mapframe-height', 'mapframe-id', 'mapframe-length_km', 'mapframe-length_mi', 'mapframe-marker', 'mapframe-marker-color', 'mapframe-marker-colour', 'mapframe-point', 'mapframe-population', 'mapframe-shape', 'mapframe-shape-fill', 'mapframe-shape-fill-opacity', 'mapframe-stroke-color', 'mapframe-stroke-colour', 'mapframe-stroke-width', 'mapframe-switcher', 'mapframe-type', 'mapframe-width', 'mapframe-wikidata', 'mapframe-zoom' } local function trim(s) return s:match('^%s*(.-)%s*$') end local function isnotempty(s) return s and s:match('%S') end local function clean(text) -- Return text cleaned for display and truncated if too long. -- Strip markers are replaced with dummy text representing the original wikitext. local pos, truncated local function truncate(text) if truncated then return '' end if mw.ustring.len(text) > 25 then truncated = true text = mw.ustring.sub(text, 1, 25) .. '...' end return mw.text.nowiki(text) end local parts = {} for before, tag, remainder in text:gmatch('([^\127]*)\127[^\127]*%-(%l+)%-[^\127]*\127()') do pos = remainder table.insert(parts, truncate(before) .. '&lt;' .. tag .. '&gt;...&lt;/' .. tag .. '&gt;') end table.insert(parts, truncate(text:sub(pos or 1))) return table.concat(parts) end function p._check(args, pargs) if type(args) ~= "table" or type(pargs) ~= "table" then -- TODO: error handling return end -- create the list of known args, regular expressions, and the return string local knownargs = {} if isnotempty(args['mapframe_args']) then for _, v in ipairs(mapframe_params) do knownargs[v] = 1 end end local regexps = {} for k, v in pairs(args) do if type(k) == 'number' then v = trim(v) knownargs[v] = 1 elseif k:find('^regexp[1-9][0-9]*$') then table.insert(regexps, '^' .. v .. '$') end end -- loop over the parent args, and make sure they are on the list local ignoreblank = isnotempty(args['ignoreblank']) local showblankpos = isnotempty(args['showblankpositional']) local values = {} for k, v in pairs(pargs) do if type(k) == 'string' and knownargs[k] == nil then local knownflag = false for _, regexp in ipairs(regexps) do if mw.ustring.match(k, regexp) then knownflag = true break end end if not knownflag and ( not ignoreblank or isnotempty(v) ) then table.insert(values, clean(k)) end elseif type(k) == 'number' and knownargs[tostring(k)] == nil then local knownflag = false for _, regexp in ipairs(regexps) do if mw.ustring.match(tostring(k), regexp) then knownflag = true break end end if not knownflag and ( showblankpos or isnotempty(v) ) then table.insert(values, k .. ' = ' .. clean(v)) end end end -- add results to the output tables local res = {} if #values > 0 then local unknown_text = args['unknown'] or 'Found _VALUE_, ' if mw.getCurrentFrame():preprocess( "{{REVISIONID}}" ) == "" then local preview_text = args['preview'] if isnotempty(preview_text) then preview_text = require('Module:If preview')._warning({preview_text}) elseif preview_text == nil then preview_text = unknown_text end unknown_text = preview_text end for _, v in pairs(values) do -- Fix odd bug for | = which gets stripped to the empty string and -- breaks category links if v == '' then v = ' ' end -- avoid error with v = 'example%2' ("invalid capture index") local r = unknown_text:gsub('_VALUE_', {_VALUE_ = v}) table.insert(res, r) end end return table.concat(res) end function p.check(frame) local args = frame.args local pargs = frame:getParent().args return p._check(args, pargs) end return p 1hjemu98zajwimptzieho9nxzbrn6ir 803636 803635 2025-10-17T10:40:59Z en>Joy 0 document line and shape stroke-* params for mapframe 803636 Scribunto text/plain -- This module may be used to compare the arguments passed to the parent -- with a list of arguments, returning a specified result if an argument is -- not on the list require ('strict'); local p = {} local mapframe_params = { 'mapframe', 'mapframe-area_km2', 'mapframe-area_mi2', 'mapframe-caption', 'mapframe-coord', 'mapframe-coordinates', 'mapframe-custom', 'mapframe-frame-coord', 'mapframe-frame-coordinates', 'mapframe-frame-height', 'mapframe-frame-width', 'mapframe-geomask', 'mapframe-geomask-fill', 'mapframe-geomask-fill-opacity', 'mapframe-geomask-stroke-color', 'mapframe-geomask-stroke-colour', 'mapframe-geomask-stroke-width', 'mapframe-height', 'mapframe-id', 'mapframe-length_km', 'mapframe-length_mi', 'mapframe-line-stroke-color', 'mapframe-line-stroke-colour', 'mapframe-marker', 'mapframe-marker-color', 'mapframe-marker-colour', 'mapframe-point', 'mapframe-population', 'mapframe-shape', 'mapframe-shape-fill', 'mapframe-shape-fill-opacity', 'mapframe-shape-stroke-color', 'mapframe-shape-stroke-colour', 'mapframe-stroke-color', 'mapframe-stroke-colour', 'mapframe-stroke-width', 'mapframe-switcher', 'mapframe-type', 'mapframe-width', 'mapframe-wikidata', 'mapframe-zoom' } local function trim(s) return s:match('^%s*(.-)%s*$') end local function isnotempty(s) return s and s:match('%S') end local function clean(text) -- Return text cleaned for display and truncated if too long. -- Strip markers are replaced with dummy text representing the original wikitext. local pos, truncated local function truncate(text) if truncated then return '' end if mw.ustring.len(text) > 25 then truncated = true text = mw.ustring.sub(text, 1, 25) .. '...' end return mw.text.nowiki(text) end local parts = {} for before, tag, remainder in text:gmatch('([^\127]*)\127[^\127]*%-(%l+)%-[^\127]*\127()') do pos = remainder table.insert(parts, truncate(before) .. '&lt;' .. tag .. '&gt;...&lt;/' .. tag .. '&gt;') end table.insert(parts, truncate(text:sub(pos or 1))) return table.concat(parts) end function p._check(args, pargs) if type(args) ~= "table" or type(pargs) ~= "table" then -- TODO: error handling return end -- create the list of known args, regular expressions, and the return string local knownargs = {} if isnotempty(args['mapframe_args']) then for _, v in ipairs(mapframe_params) do knownargs[v] = 1 end end local regexps = {} for k, v in pairs(args) do if type(k) == 'number' then v = trim(v) knownargs[v] = 1 elseif k:find('^regexp[1-9][0-9]*$') then table.insert(regexps, '^' .. v .. '$') end end -- loop over the parent args, and make sure they are on the list local ignoreblank = isnotempty(args['ignoreblank']) local showblankpos = isnotempty(args['showblankpositional']) local values = {} for k, v in pairs(pargs) do if type(k) == 'string' and knownargs[k] == nil then local knownflag = false for _, regexp in ipairs(regexps) do if mw.ustring.match(k, regexp) then knownflag = true break end end if not knownflag and ( not ignoreblank or isnotempty(v) ) then table.insert(values, clean(k)) end elseif type(k) == 'number' and knownargs[tostring(k)] == nil then local knownflag = false for _, regexp in ipairs(regexps) do if mw.ustring.match(tostring(k), regexp) then knownflag = true break end end if not knownflag and ( showblankpos or isnotempty(v) ) then table.insert(values, k .. ' = ' .. clean(v)) end end end -- add results to the output tables local res = {} if #values > 0 then local unknown_text = args['unknown'] or 'Found _VALUE_, ' if mw.getCurrentFrame():preprocess( "{{REVISIONID}}" ) == "" then local preview_text = args['preview'] if isnotempty(preview_text) then preview_text = require('Module:If preview')._warning({preview_text}) elseif preview_text == nil then preview_text = unknown_text end unknown_text = preview_text end for _, v in pairs(values) do -- Fix odd bug for | = which gets stripped to the empty string and -- breaks category links if v == '' then v = ' ' end -- avoid error with v = 'example%2' ("invalid capture index") local r = unknown_text:gsub('_VALUE_', {_VALUE_ = v}) table.insert(res, r) end end return table.concat(res) end function p.check(frame) local args = frame.args local pargs = frame:getParent().args return p._check(args, pargs) end return p eo62jat5hd6a7jz1ec339pjhpcal720 803637 803636 2025-10-17T10:42:12Z en>Joy 0 document missing mapframe-line parameter 803637 Scribunto text/plain -- This module may be used to compare the arguments passed to the parent -- with a list of arguments, returning a specified result if an argument is -- not on the list require ('strict'); local p = {} local mapframe_params = { 'mapframe', 'mapframe-area_km2', 'mapframe-area_mi2', 'mapframe-caption', 'mapframe-coord', 'mapframe-coordinates', 'mapframe-custom', 'mapframe-frame-coord', 'mapframe-frame-coordinates', 'mapframe-frame-height', 'mapframe-frame-width', 'mapframe-geomask', 'mapframe-geomask-fill', 'mapframe-geomask-fill-opacity', 'mapframe-geomask-stroke-color', 'mapframe-geomask-stroke-colour', 'mapframe-geomask-stroke-width', 'mapframe-height', 'mapframe-id', 'mapframe-length_km', 'mapframe-length_mi', 'mapframe-line', 'mapframe-line-stroke-color', 'mapframe-line-stroke-colour', 'mapframe-marker', 'mapframe-marker-color', 'mapframe-marker-colour', 'mapframe-point', 'mapframe-population', 'mapframe-shape', 'mapframe-shape-fill', 'mapframe-shape-fill-opacity', 'mapframe-shape-stroke-color', 'mapframe-shape-stroke-colour', 'mapframe-stroke-color', 'mapframe-stroke-colour', 'mapframe-stroke-width', 'mapframe-switcher', 'mapframe-type', 'mapframe-width', 'mapframe-wikidata', 'mapframe-zoom' } local function trim(s) return s:match('^%s*(.-)%s*$') end local function isnotempty(s) return s and s:match('%S') end local function clean(text) -- Return text cleaned for display and truncated if too long. -- Strip markers are replaced with dummy text representing the original wikitext. local pos, truncated local function truncate(text) if truncated then return '' end if mw.ustring.len(text) > 25 then truncated = true text = mw.ustring.sub(text, 1, 25) .. '...' end return mw.text.nowiki(text) end local parts = {} for before, tag, remainder in text:gmatch('([^\127]*)\127[^\127]*%-(%l+)%-[^\127]*\127()') do pos = remainder table.insert(parts, truncate(before) .. '&lt;' .. tag .. '&gt;...&lt;/' .. tag .. '&gt;') end table.insert(parts, truncate(text:sub(pos or 1))) return table.concat(parts) end function p._check(args, pargs) if type(args) ~= "table" or type(pargs) ~= "table" then -- TODO: error handling return end -- create the list of known args, regular expressions, and the return string local knownargs = {} if isnotempty(args['mapframe_args']) then for _, v in ipairs(mapframe_params) do knownargs[v] = 1 end end local regexps = {} for k, v in pairs(args) do if type(k) == 'number' then v = trim(v) knownargs[v] = 1 elseif k:find('^regexp[1-9][0-9]*$') then table.insert(regexps, '^' .. v .. '$') end end -- loop over the parent args, and make sure they are on the list local ignoreblank = isnotempty(args['ignoreblank']) local showblankpos = isnotempty(args['showblankpositional']) local values = {} for k, v in pairs(pargs) do if type(k) == 'string' and knownargs[k] == nil then local knownflag = false for _, regexp in ipairs(regexps) do if mw.ustring.match(k, regexp) then knownflag = true break end end if not knownflag and ( not ignoreblank or isnotempty(v) ) then table.insert(values, clean(k)) end elseif type(k) == 'number' and knownargs[tostring(k)] == nil then local knownflag = false for _, regexp in ipairs(regexps) do if mw.ustring.match(tostring(k), regexp) then knownflag = true break end end if not knownflag and ( showblankpos or isnotempty(v) ) then table.insert(values, k .. ' = ' .. clean(v)) end end end -- add results to the output tables local res = {} if #values > 0 then local unknown_text = args['unknown'] or 'Found _VALUE_, ' if mw.getCurrentFrame():preprocess( "{{REVISIONID}}" ) == "" then local preview_text = args['preview'] if isnotempty(preview_text) then preview_text = require('Module:If preview')._warning({preview_text}) elseif preview_text == nil then preview_text = unknown_text end unknown_text = preview_text end for _, v in pairs(values) do -- Fix odd bug for | = which gets stripped to the empty string and -- breaks category links if v == '' then v = ' ' end -- avoid error with v = 'example%2' ("invalid capture index") local r = unknown_text:gsub('_VALUE_', {_VALUE_ = v}) table.insert(res, r) end end return table.concat(res) end function p.check(frame) local args = frame.args local pargs = frame:getParent().args return p._check(args, pargs) end return p 2l8d1a5bs6d84ovkw5hud1zhit3uisw 803638 803637 2025-10-17T11:01:38Z en>Joy 0 document missing mapframe parameter aliases 803638 Scribunto text/plain -- This module may be used to compare the arguments passed to the parent -- with a list of arguments, returning a specified result if an argument is -- not on the list require ('strict'); local p = {} local mapframe_params = { 'coord', 'coordinates', 'id', 'qid', 'mapframe', 'mapframe-area_km2', 'mapframe-area_mi2', 'mapframe-caption', 'mapframe-coord', 'mapframe-coordinates', 'mapframe-custom', 'mapframe-frame-coord', 'mapframe-frame-coordinates', 'mapframe-frame-height', 'mapframe-frame-width', 'mapframe-geomask', 'mapframe-geomask-fill', 'mapframe-geomask-fill-opacity', 'mapframe-geomask-stroke-color', 'mapframe-geomask-stroke-colour', 'mapframe-geomask-stroke-width', 'mapframe-height', 'mapframe-id', 'mapframe-length_km', 'mapframe-length_mi', 'mapframe-line', 'mapframe-line-stroke-color', 'mapframe-line-stroke-colour', 'mapframe-marker', 'mapframe-marker-color', 'mapframe-marker-colour', 'mapframe-point', 'mapframe-population', 'mapframe-shape', 'mapframe-shape-fill', 'mapframe-shape-fill-opacity', 'mapframe-shape-stroke-color', 'mapframe-shape-stroke-colour', 'mapframe-stroke-color', 'mapframe-stroke-colour', 'mapframe-stroke-width', 'mapframe-switcher', 'mapframe-type', 'mapframe-width', 'mapframe-wikidata', 'mapframe-zoom' } local function trim(s) return s:match('^%s*(.-)%s*$') end local function isnotempty(s) return s and s:match('%S') end local function clean(text) -- Return text cleaned for display and truncated if too long. -- Strip markers are replaced with dummy text representing the original wikitext. local pos, truncated local function truncate(text) if truncated then return '' end if mw.ustring.len(text) > 25 then truncated = true text = mw.ustring.sub(text, 1, 25) .. '...' end return mw.text.nowiki(text) end local parts = {} for before, tag, remainder in text:gmatch('([^\127]*)\127[^\127]*%-(%l+)%-[^\127]*\127()') do pos = remainder table.insert(parts, truncate(before) .. '&lt;' .. tag .. '&gt;...&lt;/' .. tag .. '&gt;') end table.insert(parts, truncate(text:sub(pos or 1))) return table.concat(parts) end function p._check(args, pargs) if type(args) ~= "table" or type(pargs) ~= "table" then -- TODO: error handling return end -- create the list of known args, regular expressions, and the return string local knownargs = {} if isnotempty(args['mapframe_args']) then for _, v in ipairs(mapframe_params) do knownargs[v] = 1 end end local regexps = {} for k, v in pairs(args) do if type(k) == 'number' then v = trim(v) knownargs[v] = 1 elseif k:find('^regexp[1-9][0-9]*$') then table.insert(regexps, '^' .. v .. '$') end end -- loop over the parent args, and make sure they are on the list local ignoreblank = isnotempty(args['ignoreblank']) local showblankpos = isnotempty(args['showblankpositional']) local values = {} for k, v in pairs(pargs) do if type(k) == 'string' and knownargs[k] == nil then local knownflag = false for _, regexp in ipairs(regexps) do if mw.ustring.match(k, regexp) then knownflag = true break end end if not knownflag and ( not ignoreblank or isnotempty(v) ) then table.insert(values, clean(k)) end elseif type(k) == 'number' and knownargs[tostring(k)] == nil then local knownflag = false for _, regexp in ipairs(regexps) do if mw.ustring.match(tostring(k), regexp) then knownflag = true break end end if not knownflag and ( showblankpos or isnotempty(v) ) then table.insert(values, k .. ' = ' .. clean(v)) end end end -- add results to the output tables local res = {} if #values > 0 then local unknown_text = args['unknown'] or 'Found _VALUE_, ' if mw.getCurrentFrame():preprocess( "{{REVISIONID}}" ) == "" then local preview_text = args['preview'] if isnotempty(preview_text) then preview_text = require('Module:If preview')._warning({preview_text}) elseif preview_text == nil then preview_text = unknown_text end unknown_text = preview_text end for _, v in pairs(values) do -- Fix odd bug for | = which gets stripped to the empty string and -- breaks category links if v == '' then v = ' ' end -- avoid error with v = 'example%2' ("invalid capture index") local r = unknown_text:gsub('_VALUE_', {_VALUE_ = v}) table.insert(res, r) end end return table.concat(res) end function p.check(frame) local args = frame.args local pargs = frame:getParent().args return p._check(args, pargs) end return p mf5u017ry3j5wcsi9ctzrqfash3oih5 803639 803638 2026-06-28T21:41:54Z en>MSGJ 0 add list of pushpin map parameters 803639 Scribunto text/plain -- This module may be used to compare the arguments passed to the parent -- with a list of arguments, returning a specified result if an argument is -- not on the list require ('strict'); local p = {} local pushpin_map_params = { 'coordinates', 'pushpin_caption', 'pushpin_relief', 'pushpin_label', 'pushpin_label_position', 'pushpin_label_size', 'pushpin_mark', 'pushpin_mark_size', 'pushpin_alt', 'pushpin_background', 'pushpin_map_size' } local mapframe_params = { 'coord', 'coordinates', 'id', 'qid', 'mapframe', 'mapframe-area_km2', 'mapframe-area_mi2', 'mapframe-caption', 'mapframe-coord', 'mapframe-coordinates', 'mapframe-custom', 'mapframe-frame-coord', 'mapframe-frame-coordinates', 'mapframe-frame-height', 'mapframe-frame-width', 'mapframe-geomask', 'mapframe-geomask-fill', 'mapframe-geomask-fill-opacity', 'mapframe-geomask-stroke-color', 'mapframe-geomask-stroke-colour', 'mapframe-geomask-stroke-width', 'mapframe-height', 'mapframe-id', 'mapframe-length_km', 'mapframe-length_mi', 'mapframe-line', 'mapframe-line-stroke-color', 'mapframe-line-stroke-colour', 'mapframe-marker', 'mapframe-marker-color', 'mapframe-marker-colour', 'mapframe-point', 'mapframe-population', 'mapframe-shape', 'mapframe-shape-fill', 'mapframe-shape-fill-opacity', 'mapframe-shape-stroke-color', 'mapframe-shape-stroke-colour', 'mapframe-stroke-color', 'mapframe-stroke-colour', 'mapframe-stroke-width', 'mapframe-switcher', 'mapframe-type', 'mapframe-width', 'mapframe-wikidata', 'mapframe-zoom' } local function trim(s) return s:match('^%s*(.-)%s*$') end local function isnotempty(s) return s and s:match('%S') end local function clean(text) -- Return text cleaned for display and truncated if too long. -- Strip markers are replaced with dummy text representing the original wikitext. local pos, truncated local function truncate(text) if truncated then return '' end if mw.ustring.len(text) > 25 then truncated = true text = mw.ustring.sub(text, 1, 25) .. '...' end return mw.text.nowiki(text) end local parts = {} for before, tag, remainder in text:gmatch('([^\127]*)\127[^\127]*%-(%l+)%-[^\127]*\127()') do pos = remainder table.insert(parts, truncate(before) .. '&lt;' .. tag .. '&gt;...&lt;/' .. tag .. '&gt;') end table.insert(parts, truncate(text:sub(pos or 1))) return table.concat(parts) end function p._check(args, pargs) if type(args) ~= "table" or type(pargs) ~= "table" then -- TODO: error handling return end -- create the list of known args, regular expressions, and the return string local knownargs = {} if isnotempty(args['mapframe_args']) then for _, v in ipairs(mapframe_params) do knownargs[v] = 1 end end if isnotempty(args['pushpin_map_args']) then for _, v in ipairs(pushpin_map_params) do knownargs[v] = 1 end end local regexps = {} for k, v in pairs(args) do if type(k) == 'number' then v = trim(v) knownargs[v] = 1 elseif k:find('^regexp[1-9][0-9]*$') then table.insert(regexps, '^' .. v .. '$') end end -- loop over the parent args, and make sure they are on the list local ignoreblank = isnotempty(args['ignoreblank']) local showblankpos = isnotempty(args['showblankpositional']) local values = {} for k, v in pairs(pargs) do if type(k) == 'string' and knownargs[k] == nil then local knownflag = false for _, regexp in ipairs(regexps) do if mw.ustring.match(k, regexp) then knownflag = true break end end if not knownflag and ( not ignoreblank or isnotempty(v) ) then table.insert(values, clean(k)) end elseif type(k) == 'number' and knownargs[tostring(k)] == nil then local knownflag = false for _, regexp in ipairs(regexps) do if mw.ustring.match(tostring(k), regexp) then knownflag = true break end end if not knownflag and ( showblankpos or isnotempty(v) ) then table.insert(values, k .. ' = ' .. clean(v)) end end end -- add results to the output tables local res = {} if #values > 0 then local unknown_text = args['unknown'] or 'Found _VALUE_, ' if mw.getCurrentFrame():preprocess( "{{REVISIONID}}" ) == "" then local preview_text = args['preview'] if isnotempty(preview_text) then preview_text = require('Module:If preview')._warning({preview_text}) elseif preview_text == nil then preview_text = unknown_text end unknown_text = preview_text end for _, v in pairs(values) do -- Fix odd bug for | = which gets stripped to the empty string and -- breaks category links if v == '' then v = ' ' end -- avoid error with v = 'example%2' ("invalid capture index") local r = unknown_text:gsub('_VALUE_', {_VALUE_ = v}) table.insert(res, r) end end return table.concat(res) end function p.check(frame) local args = frame.args local pargs = frame:getParent().args return p._check(args, pargs) end return p civhrks6bzupi1qw5hmyrd22dludbl6 803640 803639 2026-06-28T22:16:03Z en>MSGJ 0 +pushpin_map 803640 Scribunto text/plain -- This module may be used to compare the arguments passed to the parent -- with a list of arguments, returning a specified result if an argument is -- not on the list require ('strict'); local p = {} local pushpin_map_params = { 'coordinates', 'pushpin_caption', 'pushpin_relief', 'pushpin_label', 'pushpin_label_position', 'pushpin_label_size', 'pushpin_map', 'pushpin_mark', 'pushpin_mark_size', 'pushpin_alt', 'pushpin_background', 'pushpin_map_size' } local mapframe_params = { 'coord', 'coordinates', 'id', 'qid', 'mapframe', 'mapframe-area_km2', 'mapframe-area_mi2', 'mapframe-caption', 'mapframe-coord', 'mapframe-coordinates', 'mapframe-custom', 'mapframe-frame-coord', 'mapframe-frame-coordinates', 'mapframe-frame-height', 'mapframe-frame-width', 'mapframe-geomask', 'mapframe-geomask-fill', 'mapframe-geomask-fill-opacity', 'mapframe-geomask-stroke-color', 'mapframe-geomask-stroke-colour', 'mapframe-geomask-stroke-width', 'mapframe-height', 'mapframe-id', 'mapframe-length_km', 'mapframe-length_mi', 'mapframe-line', 'mapframe-line-stroke-color', 'mapframe-line-stroke-colour', 'mapframe-marker', 'mapframe-marker-color', 'mapframe-marker-colour', 'mapframe-point', 'mapframe-population', 'mapframe-shape', 'mapframe-shape-fill', 'mapframe-shape-fill-opacity', 'mapframe-shape-stroke-color', 'mapframe-shape-stroke-colour', 'mapframe-stroke-color', 'mapframe-stroke-colour', 'mapframe-stroke-width', 'mapframe-switcher', 'mapframe-type', 'mapframe-width', 'mapframe-wikidata', 'mapframe-zoom' } local function trim(s) return s:match('^%s*(.-)%s*$') end local function isnotempty(s) return s and s:match('%S') end local function clean(text) -- Return text cleaned for display and truncated if too long. -- Strip markers are replaced with dummy text representing the original wikitext. local pos, truncated local function truncate(text) if truncated then return '' end if mw.ustring.len(text) > 25 then truncated = true text = mw.ustring.sub(text, 1, 25) .. '...' end return mw.text.nowiki(text) end local parts = {} for before, tag, remainder in text:gmatch('([^\127]*)\127[^\127]*%-(%l+)%-[^\127]*\127()') do pos = remainder table.insert(parts, truncate(before) .. '&lt;' .. tag .. '&gt;...&lt;/' .. tag .. '&gt;') end table.insert(parts, truncate(text:sub(pos or 1))) return table.concat(parts) end function p._check(args, pargs) if type(args) ~= "table" or type(pargs) ~= "table" then -- TODO: error handling return end -- create the list of known args, regular expressions, and the return string local knownargs = {} if isnotempty(args['mapframe_args']) then for _, v in ipairs(mapframe_params) do knownargs[v] = 1 end end if isnotempty(args['pushpin_map_args']) then for _, v in ipairs(pushpin_map_params) do knownargs[v] = 1 end end local regexps = {} for k, v in pairs(args) do if type(k) == 'number' then v = trim(v) knownargs[v] = 1 elseif k:find('^regexp[1-9][0-9]*$') then table.insert(regexps, '^' .. v .. '$') end end -- loop over the parent args, and make sure they are on the list local ignoreblank = isnotempty(args['ignoreblank']) local showblankpos = isnotempty(args['showblankpositional']) local values = {} for k, v in pairs(pargs) do if type(k) == 'string' and knownargs[k] == nil then local knownflag = false for _, regexp in ipairs(regexps) do if mw.ustring.match(k, regexp) then knownflag = true break end end if not knownflag and ( not ignoreblank or isnotempty(v) ) then table.insert(values, clean(k)) end elseif type(k) == 'number' and knownargs[tostring(k)] == nil then local knownflag = false for _, regexp in ipairs(regexps) do if mw.ustring.match(tostring(k), regexp) then knownflag = true break end end if not knownflag and ( showblankpos or isnotempty(v) ) then table.insert(values, k .. ' = ' .. clean(v)) end end end -- add results to the output tables local res = {} if #values > 0 then local unknown_text = args['unknown'] or 'Found _VALUE_, ' if mw.getCurrentFrame():preprocess( "{{REVISIONID}}" ) == "" then local preview_text = args['preview'] if isnotempty(preview_text) then preview_text = require('Module:If preview')._warning({preview_text}) elseif preview_text == nil then preview_text = unknown_text end unknown_text = preview_text end for _, v in pairs(values) do -- Fix odd bug for | = which gets stripped to the empty string and -- breaks category links if v == '' then v = ' ' end -- avoid error with v = 'example%2' ("invalid capture index") local r = unknown_text:gsub('_VALUE_', {_VALUE_ = v}) table.insert(res, r) end end return table.concat(res) end function p.check(frame) local args = frame.args local pargs = frame:getParent().args return p._check(args, pargs) end return p kohqf9fxewyce9gq1kljbz0180u8sl1 803641 803640 2026-07-30T00:20:32Z SM7 3953 6 revisions imported from [[:en:Module:Check_for_unknown_parameters]] 803640 Scribunto text/plain -- This module may be used to compare the arguments passed to the parent -- with a list of arguments, returning a specified result if an argument is -- not on the list require ('strict'); local p = {} local pushpin_map_params = { 'coordinates', 'pushpin_caption', 'pushpin_relief', 'pushpin_label', 'pushpin_label_position', 'pushpin_label_size', 'pushpin_map', 'pushpin_mark', 'pushpin_mark_size', 'pushpin_alt', 'pushpin_background', 'pushpin_map_size' } local mapframe_params = { 'coord', 'coordinates', 'id', 'qid', 'mapframe', 'mapframe-area_km2', 'mapframe-area_mi2', 'mapframe-caption', 'mapframe-coord', 'mapframe-coordinates', 'mapframe-custom', 'mapframe-frame-coord', 'mapframe-frame-coordinates', 'mapframe-frame-height', 'mapframe-frame-width', 'mapframe-geomask', 'mapframe-geomask-fill', 'mapframe-geomask-fill-opacity', 'mapframe-geomask-stroke-color', 'mapframe-geomask-stroke-colour', 'mapframe-geomask-stroke-width', 'mapframe-height', 'mapframe-id', 'mapframe-length_km', 'mapframe-length_mi', 'mapframe-line', 'mapframe-line-stroke-color', 'mapframe-line-stroke-colour', 'mapframe-marker', 'mapframe-marker-color', 'mapframe-marker-colour', 'mapframe-point', 'mapframe-population', 'mapframe-shape', 'mapframe-shape-fill', 'mapframe-shape-fill-opacity', 'mapframe-shape-stroke-color', 'mapframe-shape-stroke-colour', 'mapframe-stroke-color', 'mapframe-stroke-colour', 'mapframe-stroke-width', 'mapframe-switcher', 'mapframe-type', 'mapframe-width', 'mapframe-wikidata', 'mapframe-zoom' } local function trim(s) return s:match('^%s*(.-)%s*$') end local function isnotempty(s) return s and s:match('%S') end local function clean(text) -- Return text cleaned for display and truncated if too long. -- Strip markers are replaced with dummy text representing the original wikitext. local pos, truncated local function truncate(text) if truncated then return '' end if mw.ustring.len(text) > 25 then truncated = true text = mw.ustring.sub(text, 1, 25) .. '...' end return mw.text.nowiki(text) end local parts = {} for before, tag, remainder in text:gmatch('([^\127]*)\127[^\127]*%-(%l+)%-[^\127]*\127()') do pos = remainder table.insert(parts, truncate(before) .. '&lt;' .. tag .. '&gt;...&lt;/' .. tag .. '&gt;') end table.insert(parts, truncate(text:sub(pos or 1))) return table.concat(parts) end function p._check(args, pargs) if type(args) ~= "table" or type(pargs) ~= "table" then -- TODO: error handling return end -- create the list of known args, regular expressions, and the return string local knownargs = {} if isnotempty(args['mapframe_args']) then for _, v in ipairs(mapframe_params) do knownargs[v] = 1 end end if isnotempty(args['pushpin_map_args']) then for _, v in ipairs(pushpin_map_params) do knownargs[v] = 1 end end local regexps = {} for k, v in pairs(args) do if type(k) == 'number' then v = trim(v) knownargs[v] = 1 elseif k:find('^regexp[1-9][0-9]*$') then table.insert(regexps, '^' .. v .. '$') end end -- loop over the parent args, and make sure they are on the list local ignoreblank = isnotempty(args['ignoreblank']) local showblankpos = isnotempty(args['showblankpositional']) local values = {} for k, v in pairs(pargs) do if type(k) == 'string' and knownargs[k] == nil then local knownflag = false for _, regexp in ipairs(regexps) do if mw.ustring.match(k, regexp) then knownflag = true break end end if not knownflag and ( not ignoreblank or isnotempty(v) ) then table.insert(values, clean(k)) end elseif type(k) == 'number' and knownargs[tostring(k)] == nil then local knownflag = false for _, regexp in ipairs(regexps) do if mw.ustring.match(tostring(k), regexp) then knownflag = true break end end if not knownflag and ( showblankpos or isnotempty(v) ) then table.insert(values, k .. ' = ' .. clean(v)) end end end -- add results to the output tables local res = {} if #values > 0 then local unknown_text = args['unknown'] or 'Found _VALUE_, ' if mw.getCurrentFrame():preprocess( "{{REVISIONID}}" ) == "" then local preview_text = args['preview'] if isnotempty(preview_text) then preview_text = require('Module:If preview')._warning({preview_text}) elseif preview_text == nil then preview_text = unknown_text end unknown_text = preview_text end for _, v in pairs(values) do -- Fix odd bug for | = which gets stripped to the empty string and -- breaks category links if v == '' then v = ' ' end -- avoid error with v = 'example%2' ("invalid capture index") local r = unknown_text:gsub('_VALUE_', {_VALUE_ = v}) table.insert(res, r) end end return table.concat(res) end function p.check(frame) local args = frame.args local pargs = frame:getParent().args return p._check(args, pargs) end return p kohqf9fxewyce9gq1kljbz0180u8sl1 टेम्पलेट:PAGENAMEBASE 10 15955 803652 141331 2016-03-06T09:37:56Z en>Cabayi 0 per edit request & discussion 803652 wikitext text/x-wiki {{{{{|safesubst:}}}#Invoke:String|replace|{{{1|{{{{{|safesubst:}}}PAGENAME}}}}}|%s+%b()$||1|false}}<noinclude> {{documentation}} </noinclude> sam8aetb68ekom4findu6pp900ngldr 803653 297013 2024-10-28T10:45:21Z en>Dinoguy1000 0 fix "|=foo" bug 803653 wikitext text/x-wiki {{safesubst:<noinclude/>#invoke:String|replace|{{{1|{{safesubst:<noinclude/>PAGENAME}}}}}|%s+%b()$||1|false}}<noinclude> {{documentation}} </noinclude> hfmr5rmcuhdncvzny8x6n6ob8hddcpq 803654 803653 2026-07-30T00:22:05Z SM7 3953 2 revisions imported from [[:en:Template:PAGENAMEBASE]] 803653 wikitext text/x-wiki {{safesubst:<noinclude/>#invoke:String|replace|{{{1|{{safesubst:<noinclude/>PAGENAME}}}}}|%s+%b()$||1|false}}<noinclude> {{documentation}} </noinclude> hfmr5rmcuhdncvzny8x6n6ob8hddcpq Module:String 828 15958 803564 778438 2025-08-11T23:47:14Z en>Pppery 0 Copy findpagetext from [[Module:String2]] 803564 Scribunto text/plain --[[ This module is intended to provide access to basic string functions. Most of the functions provided here can be invoked with named parameters, unnamed parameters, or a mixture. If named parameters are used, Mediawiki will automatically remove any leading or trailing whitespace from the parameter. Depending on the intended use, it may be advantageous to either preserve or remove such whitespace. Global options ignore_errors: If set to 'true' or 1, any error condition will result in an empty string being returned rather than an error message. error_category: If an error occurs, specifies the name of a category to include with the error message. The default category is [Category:Errors reported by Module String]. no_category: If set to 'true' or 1, no category will be added if an error is generated. Unit tests for this module are available at Module:String/tests. ]] local str = {} --[[ len This function returns the length of the target string. Usage: {{#invoke:String|len|target_string|}} OR {{#invoke:String|len|s=target_string}} Parameters s: The string whose length to report If invoked using named parameters, Mediawiki will automatically remove any leading or trailing whitespace from the target string. ]] function str.len( frame ) local new_args = str._getParameters( frame.args, {'s'} ) local s = new_args['s'] or '' return mw.ustring.len( s ) end --[[ sub This function returns a substring of the target string at specified indices. Usage: {{#invoke:String|sub|target_string|start_index|end_index}} OR {{#invoke:String|sub|s=target_string|i=start_index|j=end_index}} Parameters s: The string to return a subset of i: The first index of the substring to return, defaults to 1. j: The last index of the string to return, defaults to the last character. The first character of the string is assigned an index of 1. If either i or j is a negative value, it is interpreted the same as selecting a character by counting from the end of the string. Hence, a value of -1 is the same as selecting the last character of the string. If the requested indices are out of range for the given string, an error is reported. ]] function str.sub( frame ) local new_args = str._getParameters( frame.args, { 's', 'i', 'j' } ) local s = new_args['s'] or '' local i = tonumber( new_args['i'] ) or 1 local j = tonumber( new_args['j'] ) or -1 local len = mw.ustring.len( s ) -- Convert negatives for range checking if i < 0 then i = len + i + 1 end if j < 0 then j = len + j + 1 end if i > len or j > len or i < 1 or j < 1 then return str._error( 'String subset index out of range' ) end if j < i then return str._error( 'String subset indices out of order' ) end return mw.ustring.sub( s, i, j ) end --[[ This function implements that features of {{str sub old}} and is kept in order to maintain these older templates. ]] function str.sublength( frame ) local i = tonumber( frame.args.i ) or 0 local len = tonumber( frame.args.len ) return mw.ustring.sub( frame.args.s, i + 1, len and ( i + len ) ) end --[[ _match This function returns a substring from the source string that matches a specified pattern. It is exported for use in other modules Usage: strmatch = require("Module:String")._match sresult = strmatch( s, pattern, start, match, plain, nomatch ) Parameters s: The string to search pattern: The pattern or string to find within the string start: The index within the source string to start the search. The first character of the string has index 1. Defaults to 1. match: In some cases it may be possible to make multiple matches on a single string. This specifies which match to return, where the first match is match= 1. If a negative number is specified then a match is returned counting from the last match. Hence match = -1 is the same as requesting the last match. Defaults to 1. plain: A flag indicating that the pattern should be understood as plain text. Defaults to false. nomatch: If no match is found, output the "nomatch" value rather than an error. For information on constructing Lua patterns, a form of [regular expression], see: * http://www.lua.org/manual/5.1/manual.html#5.4.1 * http://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#Patterns * http://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#Ustring_patterns ]] -- This sub-routine is exported for use in other modules function str._match( s, pattern, start, match_index, plain_flag, nomatch ) if s == '' then return str._error( 'Target string is empty' ) end if pattern == '' then return str._error( 'Pattern string is empty' ) end start = tonumber(start) or 1 if math.abs(start) < 1 or math.abs(start) > mw.ustring.len( s ) then return str._error( 'Requested start is out of range' ) end if match_index == 0 then return str._error( 'Match index is out of range' ) end if plain_flag then pattern = str._escapePattern( pattern ) end local result if match_index == 1 then -- Find first match is simple case result = mw.ustring.match( s, pattern, start ) else if start > 1 then s = mw.ustring.sub( s, start ) end local iterator = mw.ustring.gmatch(s, pattern) if match_index > 0 then -- Forward search for w in iterator do match_index = match_index - 1 if match_index == 0 then result = w break end end else -- Reverse search local result_table = {} local count = 1 for w in iterator do result_table[count] = w count = count + 1 end result = result_table[ count + match_index ] end end if result == nil then if nomatch == nil then return str._error( 'Match not found' ) else return nomatch end else return result end end --[[ match This function returns a substring from the source string that matches a specified pattern. Usage: {{#invoke:String|match|source_string|pattern_string|start_index|match_number|plain_flag|nomatch_output}} OR {{#invoke:String|match|s=source_string|pattern=pattern_string|start=start_index |match=match_number|plain=plain_flag|nomatch=nomatch_output}} Parameters s: The string to search pattern: The pattern or string to find within the string start: The index within the source string to start the search. The first character of the string has index 1. Defaults to 1. match: In some cases it may be possible to make multiple matches on a single string. This specifies which match to return, where the first match is match= 1. If a negative number is specified then a match is returned counting from the last match. Hence match = -1 is the same as requesting the last match. Defaults to 1. plain: A flag indicating that the pattern should be understood as plain text. Defaults to false. nomatch: If no match is found, output the "nomatch" value rather than an error. If invoked using named parameters, Mediawiki will automatically remove any leading or trailing whitespace from each string. In some circumstances this is desirable, in other cases one may want to preserve the whitespace. If the match_number or start_index are out of range for the string being queried, then this function generates an error. An error is also generated if no match is found. If one adds the parameter ignore_errors=true, then the error will be suppressed and an empty string will be returned on any failure. For information on constructing Lua patterns, a form of [regular expression], see: * http://www.lua.org/manual/5.1/manual.html#5.4.1 * http://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#Patterns * http://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#Ustring_patterns ]] -- This is the entry point for #invoke:String|match function str.match( frame ) local new_args = str._getParameters( frame.args, {'s', 'pattern', 'start', 'match', 'plain', 'nomatch'} ) local s = new_args['s'] or '' local start = tonumber( new_args['start'] ) or 1 local plain_flag = str._getBoolean( new_args['plain'] or false ) local pattern = new_args['pattern'] or '' local match_index = math.floor( tonumber(new_args['match']) or 1 ) local nomatch = new_args['nomatch'] return str._match( s, pattern, start, match_index, plain_flag, nomatch ) end --[[ pos This function returns a single character from the target string at position pos. Usage: {{#invoke:String|pos|target_string|index_value}} OR {{#invoke:String|pos|target=target_string|pos=index_value}} Parameters target: The string to search pos: The index for the character to return If invoked using named parameters, Mediawiki will automatically remove any leading or trailing whitespace from the target string. In some circumstances this is desirable, in other cases one may want to preserve the whitespace. The first character has an index value of 1. If one requests a negative value, this function will select a character by counting backwards from the end of the string. In other words pos = -1 is the same as asking for the last character. A requested value of zero, or a value greater than the length of the string returns an error. ]] function str.pos( frame ) local new_args = str._getParameters( frame.args, {'target', 'pos'} ) local target_str = new_args['target'] or '' local pos = tonumber( new_args['pos'] ) or 0 if pos == 0 or math.abs(pos) > mw.ustring.len( target_str ) then return str._error( 'String index out of range' ) end return mw.ustring.sub( target_str, pos, pos ) end --[[ str_find This function duplicates the behavior of {{str_find}}, including all of its quirks. This is provided in order to support existing templates, but is NOT RECOMMENDED for new code and templates. New code is recommended to use the "find" function instead. Returns the first index in "source" that is a match to "target". Indexing is 1-based, and the function returns -1 if the "target" string is not present in "source". Important Note: If the "target" string is empty / missing, this function returns a value of "1", which is generally unexpected behavior, and must be accounted for separatetly. ]] function str.str_find( frame ) local new_args = str._getParameters( frame.args, {'source', 'target'} ) local source_str = new_args['source'] or '' local target_str = new_args['target'] or '' if target_str == '' then return 1 end local start = mw.ustring.find( source_str, target_str, 1, true ) if start == nil then start = -1 end return start end --[[ find This function allows one to search for a target string or pattern within another string. Usage: {{#invoke:String|find|source_str|target_string|start_index|plain_flag}} OR {{#invoke:String|find|source=source_str|target=target_str|start=start_index|plain=plain_flag}} Parameters source: The string to search target: The string or pattern to find within source start: The index within the source string to start the search, defaults to 1 plain: Boolean flag indicating that target should be understood as plain text and not as a Lua style regular expression, defaults to true If invoked using named parameters, Mediawiki will automatically remove any leading or trailing whitespace from the parameter. In some circumstances this is desirable, in other cases one may want to preserve the whitespace. This function returns the first index >= "start" where "target" can be found within "source". Indices are 1-based. If "target" is not found, then this function returns 0. If either "source" or "target" are missing / empty, this function also returns 0. This function should be safe for UTF-8 strings. ]] function str.find( frame ) local new_args = str._getParameters( frame.args, {'source', 'target', 'start', 'plain' } ) local source_str = new_args['source'] or '' local pattern = new_args['target'] or '' local start_pos = tonumber(new_args['start']) or 1 local plain = new_args['plain'] or true if source_str == '' or pattern == '' then return 0 end plain = str._getBoolean( plain ) local start = mw.ustring.find( source_str, pattern, start_pos, plain ) if start == nil then start = 0 end return start end --[[ replace This function allows one to replace a target string or pattern within another string. Usage: {{#invoke:String|replace|source_str|pattern_string|replace_string|replacement_count|plain_flag}} OR {{#invoke:String|replace|source=source_string|pattern=pattern_string|replace=replace_string| count=replacement_count|plain=plain_flag}} Parameters source: The string to search pattern: The string or pattern to find within source replace: The replacement text count: The number of occurences to replace, defaults to all. plain: Boolean flag indicating that pattern should be understood as plain text and not as a Lua style regular expression, defaults to true ]] function str.replace( frame ) local new_args = str._getParameters( frame.args, {'source', 'pattern', 'replace', 'count', 'plain' } ) local source_str = new_args['source'] or '' local pattern = new_args['pattern'] or '' local replace = new_args['replace'] or '' local count = tonumber( new_args['count'] ) local plain = new_args['plain'] or true if source_str == '' or pattern == '' then return source_str end plain = str._getBoolean( plain ) if plain then pattern = str._escapePattern( pattern ) replace = string.gsub( replace, "%%", "%%%%" ) --Only need to escape replacement sequences. end local result if count ~= nil then result = mw.ustring.gsub( source_str, pattern, replace, count ) else result = mw.ustring.gsub( source_str, pattern, replace ) end return result end --[[ simple function to pipe string.rep to templates. ]] function str.rep( frame ) local repetitions = tonumber( frame.args[2] ) if not repetitions then return str._error( 'function rep expects a number as second parameter, received "' .. ( frame.args[2] or '' ) .. '"' ) end return string.rep( frame.args[1] or '', repetitions ) end --[[ escapePattern This function escapes special characters from a Lua string pattern. See [1] for details on how patterns work. [1] https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#Patterns Usage: {{#invoke:String|escapePattern|pattern_string}} Parameters pattern_string: The pattern string to escape. ]] function str.escapePattern( frame ) local pattern_str = frame.args[1] if not pattern_str then return str._error( 'No pattern string specified' ) end local result = str._escapePattern( pattern_str ) return result end --[[ count This function counts the number of occurrences of one string in another. ]] function str.count(frame) local args = str._getParameters(frame.args, {'source', 'pattern', 'plain'}) local source = args.source or '' local pattern = args.pattern or '' local plain = str._getBoolean(args.plain or true) if plain then pattern = str._escapePattern(pattern) end local _, count = mw.ustring.gsub(source, pattern, '') return count end --[[ endswith This function determines whether a string ends with another string. ]] function str.endswith(frame) local args = str._getParameters(frame.args, {'source', 'pattern'}) local source = args.source or '' local pattern = args.pattern or '' if pattern == '' then -- All strings end with the empty string. return "yes" end if mw.ustring.sub(source, -mw.ustring.len(pattern), -1) == pattern then return "yes" else return "" end end --[[ join Join all non empty arguments together; the first argument is the separator. Usage: {{#invoke:String|join|sep|one|two|three}} ]] function str.join(frame) local args = {} local sep for _, v in ipairs( frame.args ) do if sep then if v ~= '' then table.insert(args, v) end else sep = v end end return table.concat( args, sep or '' ) end -- findpagetext returns the position of a piece of text in a page -- First positional parameter or |text is the search text -- Optional parameter |title is the page title, defaults to current page -- Optional parameter |plain is either true for plain search (default) or false for Lua pattern search -- Optional parameter |nomatch is the return value when no match is found; default is nil function str._findpagetext(args) -- process parameters local nomatch = args.nomatch or "" if nomatch == "" then nomatch = nil end -- local text = mw.text.trim(args[1] or args.text or "") if text == "" then return nil end -- local title = args.title or "" local titleobj if title == "" then titleobj = mw.title.getCurrentTitle() else titleobj = mw.title.new(title) end -- local plain = args.plain or "" if plain:sub(1, 1) == "f" then plain = false else plain = true end -- get the page content and look for 'text' - return position or nomatch local content = titleobj and titleobj:getContent() return content and mw.ustring.find(content, text, 1, plain) or nomatch end function str.findpagetext(frame) local args = frame.args local pargs = frame:getParent().args for k, v in pairs(pargs) do args[k] = v end if not (args[1] or args.text) then return nil end -- just the first value return (p._findpagetext(args)) end --[[ Helper function that populates the argument list given that user may need to use a mix of named and unnamed parameters. This is relevant because named parameters are not identical to unnamed parameters due to string trimming, and when dealing with strings we sometimes want to either preserve or remove that whitespace depending on the application. ]] function str._getParameters( frame_args, arg_list ) local new_args = {} local index = 1 local value for _, arg in ipairs( arg_list ) do value = frame_args[arg] if value == nil then value = frame_args[index] index = index + 1 end new_args[arg] = value end return new_args end --[[ Helper function to handle error messages. ]] function str._error( error_str ) local frame = mw.getCurrentFrame() local error_category = frame.args.error_category or 'Errors reported by Module String' local ignore_errors = frame.args.ignore_errors or false local no_category = frame.args.no_category or false if str._getBoolean(ignore_errors) then return '' end local error_str = '<strong class="error">String Module Error: ' .. error_str .. '</strong>' if error_category ~= '' and not str._getBoolean( no_category ) then error_str = '[[Category:' .. error_category .. ']]' .. error_str end return error_str end --[[ Helper Function to interpret boolean strings ]] function str._getBoolean( boolean_str ) local boolean_value if type( boolean_str ) == 'string' then boolean_str = boolean_str:lower() if boolean_str == 'false' or boolean_str == 'no' or boolean_str == '0' or boolean_str == '' then boolean_value = false else boolean_value = true end elseif type( boolean_str ) == 'boolean' then boolean_value = boolean_str else error( 'No boolean value found' ) end return boolean_value end --[[ Helper function that escapes all pattern characters so that they will be treated as plain text. ]] function str._escapePattern( pattern_str ) return ( string.gsub( pattern_str, "[%(%)%.%%%+%-%*%?%[%^%$%]]", "%%%0" ) ) end return str eypp2ycor2x5h347mkrbxut7kqyr5ai 803565 803564 2025-08-11T23:48:56Z en>Pppery 0 Fix 803565 Scribunto text/plain --[[ This module is intended to provide access to basic string functions. Most of the functions provided here can be invoked with named parameters, unnamed parameters, or a mixture. If named parameters are used, Mediawiki will automatically remove any leading or trailing whitespace from the parameter. Depending on the intended use, it may be advantageous to either preserve or remove such whitespace. Global options ignore_errors: If set to 'true' or 1, any error condition will result in an empty string being returned rather than an error message. error_category: If an error occurs, specifies the name of a category to include with the error message. The default category is [Category:Errors reported by Module String]. no_category: If set to 'true' or 1, no category will be added if an error is generated. Unit tests for this module are available at Module:String/tests. ]] local str = {} --[[ len This function returns the length of the target string. Usage: {{#invoke:String|len|target_string|}} OR {{#invoke:String|len|s=target_string}} Parameters s: The string whose length to report If invoked using named parameters, Mediawiki will automatically remove any leading or trailing whitespace from the target string. ]] function str.len( frame ) local new_args = str._getParameters( frame.args, {'s'} ) local s = new_args['s'] or '' return mw.ustring.len( s ) end --[[ sub This function returns a substring of the target string at specified indices. Usage: {{#invoke:String|sub|target_string|start_index|end_index}} OR {{#invoke:String|sub|s=target_string|i=start_index|j=end_index}} Parameters s: The string to return a subset of i: The first index of the substring to return, defaults to 1. j: The last index of the string to return, defaults to the last character. The first character of the string is assigned an index of 1. If either i or j is a negative value, it is interpreted the same as selecting a character by counting from the end of the string. Hence, a value of -1 is the same as selecting the last character of the string. If the requested indices are out of range for the given string, an error is reported. ]] function str.sub( frame ) local new_args = str._getParameters( frame.args, { 's', 'i', 'j' } ) local s = new_args['s'] or '' local i = tonumber( new_args['i'] ) or 1 local j = tonumber( new_args['j'] ) or -1 local len = mw.ustring.len( s ) -- Convert negatives for range checking if i < 0 then i = len + i + 1 end if j < 0 then j = len + j + 1 end if i > len or j > len or i < 1 or j < 1 then return str._error( 'String subset index out of range' ) end if j < i then return str._error( 'String subset indices out of order' ) end return mw.ustring.sub( s, i, j ) end --[[ This function implements that features of {{str sub old}} and is kept in order to maintain these older templates. ]] function str.sublength( frame ) local i = tonumber( frame.args.i ) or 0 local len = tonumber( frame.args.len ) return mw.ustring.sub( frame.args.s, i + 1, len and ( i + len ) ) end --[[ _match This function returns a substring from the source string that matches a specified pattern. It is exported for use in other modules Usage: strmatch = require("Module:String")._match sresult = strmatch( s, pattern, start, match, plain, nomatch ) Parameters s: The string to search pattern: The pattern or string to find within the string start: The index within the source string to start the search. The first character of the string has index 1. Defaults to 1. match: In some cases it may be possible to make multiple matches on a single string. This specifies which match to return, where the first match is match= 1. If a negative number is specified then a match is returned counting from the last match. Hence match = -1 is the same as requesting the last match. Defaults to 1. plain: A flag indicating that the pattern should be understood as plain text. Defaults to false. nomatch: If no match is found, output the "nomatch" value rather than an error. For information on constructing Lua patterns, a form of [regular expression], see: * http://www.lua.org/manual/5.1/manual.html#5.4.1 * http://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#Patterns * http://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#Ustring_patterns ]] -- This sub-routine is exported for use in other modules function str._match( s, pattern, start, match_index, plain_flag, nomatch ) if s == '' then return str._error( 'Target string is empty' ) end if pattern == '' then return str._error( 'Pattern string is empty' ) end start = tonumber(start) or 1 if math.abs(start) < 1 or math.abs(start) > mw.ustring.len( s ) then return str._error( 'Requested start is out of range' ) end if match_index == 0 then return str._error( 'Match index is out of range' ) end if plain_flag then pattern = str._escapePattern( pattern ) end local result if match_index == 1 then -- Find first match is simple case result = mw.ustring.match( s, pattern, start ) else if start > 1 then s = mw.ustring.sub( s, start ) end local iterator = mw.ustring.gmatch(s, pattern) if match_index > 0 then -- Forward search for w in iterator do match_index = match_index - 1 if match_index == 0 then result = w break end end else -- Reverse search local result_table = {} local count = 1 for w in iterator do result_table[count] = w count = count + 1 end result = result_table[ count + match_index ] end end if result == nil then if nomatch == nil then return str._error( 'Match not found' ) else return nomatch end else return result end end --[[ match This function returns a substring from the source string that matches a specified pattern. Usage: {{#invoke:String|match|source_string|pattern_string|start_index|match_number|plain_flag|nomatch_output}} OR {{#invoke:String|match|s=source_string|pattern=pattern_string|start=start_index |match=match_number|plain=plain_flag|nomatch=nomatch_output}} Parameters s: The string to search pattern: The pattern or string to find within the string start: The index within the source string to start the search. The first character of the string has index 1. Defaults to 1. match: In some cases it may be possible to make multiple matches on a single string. This specifies which match to return, where the first match is match= 1. If a negative number is specified then a match is returned counting from the last match. Hence match = -1 is the same as requesting the last match. Defaults to 1. plain: A flag indicating that the pattern should be understood as plain text. Defaults to false. nomatch: If no match is found, output the "nomatch" value rather than an error. If invoked using named parameters, Mediawiki will automatically remove any leading or trailing whitespace from each string. In some circumstances this is desirable, in other cases one may want to preserve the whitespace. If the match_number or start_index are out of range for the string being queried, then this function generates an error. An error is also generated if no match is found. If one adds the parameter ignore_errors=true, then the error will be suppressed and an empty string will be returned on any failure. For information on constructing Lua patterns, a form of [regular expression], see: * http://www.lua.org/manual/5.1/manual.html#5.4.1 * http://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#Patterns * http://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#Ustring_patterns ]] -- This is the entry point for #invoke:String|match function str.match( frame ) local new_args = str._getParameters( frame.args, {'s', 'pattern', 'start', 'match', 'plain', 'nomatch'} ) local s = new_args['s'] or '' local start = tonumber( new_args['start'] ) or 1 local plain_flag = str._getBoolean( new_args['plain'] or false ) local pattern = new_args['pattern'] or '' local match_index = math.floor( tonumber(new_args['match']) or 1 ) local nomatch = new_args['nomatch'] return str._match( s, pattern, start, match_index, plain_flag, nomatch ) end --[[ pos This function returns a single character from the target string at position pos. Usage: {{#invoke:String|pos|target_string|index_value}} OR {{#invoke:String|pos|target=target_string|pos=index_value}} Parameters target: The string to search pos: The index for the character to return If invoked using named parameters, Mediawiki will automatically remove any leading or trailing whitespace from the target string. In some circumstances this is desirable, in other cases one may want to preserve the whitespace. The first character has an index value of 1. If one requests a negative value, this function will select a character by counting backwards from the end of the string. In other words pos = -1 is the same as asking for the last character. A requested value of zero, or a value greater than the length of the string returns an error. ]] function str.pos( frame ) local new_args = str._getParameters( frame.args, {'target', 'pos'} ) local target_str = new_args['target'] or '' local pos = tonumber( new_args['pos'] ) or 0 if pos == 0 or math.abs(pos) > mw.ustring.len( target_str ) then return str._error( 'String index out of range' ) end return mw.ustring.sub( target_str, pos, pos ) end --[[ str_find This function duplicates the behavior of {{str_find}}, including all of its quirks. This is provided in order to support existing templates, but is NOT RECOMMENDED for new code and templates. New code is recommended to use the "find" function instead. Returns the first index in "source" that is a match to "target". Indexing is 1-based, and the function returns -1 if the "target" string is not present in "source". Important Note: If the "target" string is empty / missing, this function returns a value of "1", which is generally unexpected behavior, and must be accounted for separatetly. ]] function str.str_find( frame ) local new_args = str._getParameters( frame.args, {'source', 'target'} ) local source_str = new_args['source'] or '' local target_str = new_args['target'] or '' if target_str == '' then return 1 end local start = mw.ustring.find( source_str, target_str, 1, true ) if start == nil then start = -1 end return start end --[[ find This function allows one to search for a target string or pattern within another string. Usage: {{#invoke:String|find|source_str|target_string|start_index|plain_flag}} OR {{#invoke:String|find|source=source_str|target=target_str|start=start_index|plain=plain_flag}} Parameters source: The string to search target: The string or pattern to find within source start: The index within the source string to start the search, defaults to 1 plain: Boolean flag indicating that target should be understood as plain text and not as a Lua style regular expression, defaults to true If invoked using named parameters, Mediawiki will automatically remove any leading or trailing whitespace from the parameter. In some circumstances this is desirable, in other cases one may want to preserve the whitespace. This function returns the first index >= "start" where "target" can be found within "source". Indices are 1-based. If "target" is not found, then this function returns 0. If either "source" or "target" are missing / empty, this function also returns 0. This function should be safe for UTF-8 strings. ]] function str.find( frame ) local new_args = str._getParameters( frame.args, {'source', 'target', 'start', 'plain' } ) local source_str = new_args['source'] or '' local pattern = new_args['target'] or '' local start_pos = tonumber(new_args['start']) or 1 local plain = new_args['plain'] or true if source_str == '' or pattern == '' then return 0 end plain = str._getBoolean( plain ) local start = mw.ustring.find( source_str, pattern, start_pos, plain ) if start == nil then start = 0 end return start end --[[ replace This function allows one to replace a target string or pattern within another string. Usage: {{#invoke:String|replace|source_str|pattern_string|replace_string|replacement_count|plain_flag}} OR {{#invoke:String|replace|source=source_string|pattern=pattern_string|replace=replace_string| count=replacement_count|plain=plain_flag}} Parameters source: The string to search pattern: The string or pattern to find within source replace: The replacement text count: The number of occurences to replace, defaults to all. plain: Boolean flag indicating that pattern should be understood as plain text and not as a Lua style regular expression, defaults to true ]] function str.replace( frame ) local new_args = str._getParameters( frame.args, {'source', 'pattern', 'replace', 'count', 'plain' } ) local source_str = new_args['source'] or '' local pattern = new_args['pattern'] or '' local replace = new_args['replace'] or '' local count = tonumber( new_args['count'] ) local plain = new_args['plain'] or true if source_str == '' or pattern == '' then return source_str end plain = str._getBoolean( plain ) if plain then pattern = str._escapePattern( pattern ) replace = string.gsub( replace, "%%", "%%%%" ) --Only need to escape replacement sequences. end local result if count ~= nil then result = mw.ustring.gsub( source_str, pattern, replace, count ) else result = mw.ustring.gsub( source_str, pattern, replace ) end return result end --[[ simple function to pipe string.rep to templates. ]] function str.rep( frame ) local repetitions = tonumber( frame.args[2] ) if not repetitions then return str._error( 'function rep expects a number as second parameter, received "' .. ( frame.args[2] or '' ) .. '"' ) end return string.rep( frame.args[1] or '', repetitions ) end --[[ escapePattern This function escapes special characters from a Lua string pattern. See [1] for details on how patterns work. [1] https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#Patterns Usage: {{#invoke:String|escapePattern|pattern_string}} Parameters pattern_string: The pattern string to escape. ]] function str.escapePattern( frame ) local pattern_str = frame.args[1] if not pattern_str then return str._error( 'No pattern string specified' ) end local result = str._escapePattern( pattern_str ) return result end --[[ count This function counts the number of occurrences of one string in another. ]] function str.count(frame) local args = str._getParameters(frame.args, {'source', 'pattern', 'plain'}) local source = args.source or '' local pattern = args.pattern or '' local plain = str._getBoolean(args.plain or true) if plain then pattern = str._escapePattern(pattern) end local _, count = mw.ustring.gsub(source, pattern, '') return count end --[[ endswith This function determines whether a string ends with another string. ]] function str.endswith(frame) local args = str._getParameters(frame.args, {'source', 'pattern'}) local source = args.source or '' local pattern = args.pattern or '' if pattern == '' then -- All strings end with the empty string. return "yes" end if mw.ustring.sub(source, -mw.ustring.len(pattern), -1) == pattern then return "yes" else return "" end end --[[ join Join all non empty arguments together; the first argument is the separator. Usage: {{#invoke:String|join|sep|one|two|three}} ]] function str.join(frame) local args = {} local sep for _, v in ipairs( frame.args ) do if sep then if v ~= '' then table.insert(args, v) end else sep = v end end return table.concat( args, sep or '' ) end -- findpagetext returns the position of a piece of text in a page -- First positional parameter or |text is the search text -- Optional parameter |title is the page title, defaults to current page -- Optional parameter |plain is either true for plain search (default) or false for Lua pattern search -- Optional parameter |nomatch is the return value when no match is found; default is nil function str._findpagetext(args) -- process parameters local nomatch = args.nomatch or "" if nomatch == "" then nomatch = nil end -- local text = mw.text.trim(args[1] or args.text or "") if text == "" then return nil end -- local title = args.title or "" local titleobj if title == "" then titleobj = mw.title.getCurrentTitle() else titleobj = mw.title.new(title) end -- local plain = args.plain or "" if plain:sub(1, 1) == "f" then plain = false else plain = true end -- get the page content and look for 'text' - return position or nomatch local content = titleobj and titleobj:getContent() return content and mw.ustring.find(content, text, 1, plain) or nomatch end function str.findpagetext(frame) local args = frame.args local pargs = frame:getParent().args for k, v in pairs(pargs) do args[k] = v end if not (args[1] or args.text) then return nil end -- just the first value return (str._findpagetext(args)) end --[[ Helper function that populates the argument list given that user may need to use a mix of named and unnamed parameters. This is relevant because named parameters are not identical to unnamed parameters due to string trimming, and when dealing with strings we sometimes want to either preserve or remove that whitespace depending on the application. ]] function str._getParameters( frame_args, arg_list ) local new_args = {} local index = 1 local value for _, arg in ipairs( arg_list ) do value = frame_args[arg] if value == nil then value = frame_args[index] index = index + 1 end new_args[arg] = value end return new_args end --[[ Helper function to handle error messages. ]] function str._error( error_str ) local frame = mw.getCurrentFrame() local error_category = frame.args.error_category or 'Errors reported by Module String' local ignore_errors = frame.args.ignore_errors or false local no_category = frame.args.no_category or false if str._getBoolean(ignore_errors) then return '' end local error_str = '<strong class="error">String Module Error: ' .. error_str .. '</strong>' if error_category ~= '' and not str._getBoolean( no_category ) then error_str = '[[Category:' .. error_category .. ']]' .. error_str end return error_str end --[[ Helper Function to interpret boolean strings ]] function str._getBoolean( boolean_str ) local boolean_value if type( boolean_str ) == 'string' then boolean_str = boolean_str:lower() if boolean_str == 'false' or boolean_str == 'no' or boolean_str == '0' or boolean_str == '' then boolean_value = false else boolean_value = true end elseif type( boolean_str ) == 'boolean' then boolean_value = boolean_str else error( 'No boolean value found' ) end return boolean_value end --[[ Helper function that escapes all pattern characters so that they will be treated as plain text. ]] function str._escapePattern( pattern_str ) return ( string.gsub( pattern_str, "[%(%)%.%%%+%-%*%?%[%^%$%]]", "%%%0" ) ) end return str j3b8d4sejqnvazpfl0gk0yfaa503gli 803566 803565 2026-07-30T00:17:01Z SM7 3953 2 revisions imported from [[:en:Module:String]] 803565 Scribunto text/plain --[[ This module is intended to provide access to basic string functions. Most of the functions provided here can be invoked with named parameters, unnamed parameters, or a mixture. If named parameters are used, Mediawiki will automatically remove any leading or trailing whitespace from the parameter. Depending on the intended use, it may be advantageous to either preserve or remove such whitespace. Global options ignore_errors: If set to 'true' or 1, any error condition will result in an empty string being returned rather than an error message. error_category: If an error occurs, specifies the name of a category to include with the error message. The default category is [Category:Errors reported by Module String]. no_category: If set to 'true' or 1, no category will be added if an error is generated. Unit tests for this module are available at Module:String/tests. ]] local str = {} --[[ len This function returns the length of the target string. Usage: {{#invoke:String|len|target_string|}} OR {{#invoke:String|len|s=target_string}} Parameters s: The string whose length to report If invoked using named parameters, Mediawiki will automatically remove any leading or trailing whitespace from the target string. ]] function str.len( frame ) local new_args = str._getParameters( frame.args, {'s'} ) local s = new_args['s'] or '' return mw.ustring.len( s ) end --[[ sub This function returns a substring of the target string at specified indices. Usage: {{#invoke:String|sub|target_string|start_index|end_index}} OR {{#invoke:String|sub|s=target_string|i=start_index|j=end_index}} Parameters s: The string to return a subset of i: The first index of the substring to return, defaults to 1. j: The last index of the string to return, defaults to the last character. The first character of the string is assigned an index of 1. If either i or j is a negative value, it is interpreted the same as selecting a character by counting from the end of the string. Hence, a value of -1 is the same as selecting the last character of the string. If the requested indices are out of range for the given string, an error is reported. ]] function str.sub( frame ) local new_args = str._getParameters( frame.args, { 's', 'i', 'j' } ) local s = new_args['s'] or '' local i = tonumber( new_args['i'] ) or 1 local j = tonumber( new_args['j'] ) or -1 local len = mw.ustring.len( s ) -- Convert negatives for range checking if i < 0 then i = len + i + 1 end if j < 0 then j = len + j + 1 end if i > len or j > len or i < 1 or j < 1 then return str._error( 'String subset index out of range' ) end if j < i then return str._error( 'String subset indices out of order' ) end return mw.ustring.sub( s, i, j ) end --[[ This function implements that features of {{str sub old}} and is kept in order to maintain these older templates. ]] function str.sublength( frame ) local i = tonumber( frame.args.i ) or 0 local len = tonumber( frame.args.len ) return mw.ustring.sub( frame.args.s, i + 1, len and ( i + len ) ) end --[[ _match This function returns a substring from the source string that matches a specified pattern. It is exported for use in other modules Usage: strmatch = require("Module:String")._match sresult = strmatch( s, pattern, start, match, plain, nomatch ) Parameters s: The string to search pattern: The pattern or string to find within the string start: The index within the source string to start the search. The first character of the string has index 1. Defaults to 1. match: In some cases it may be possible to make multiple matches on a single string. This specifies which match to return, where the first match is match= 1. If a negative number is specified then a match is returned counting from the last match. Hence match = -1 is the same as requesting the last match. Defaults to 1. plain: A flag indicating that the pattern should be understood as plain text. Defaults to false. nomatch: If no match is found, output the "nomatch" value rather than an error. For information on constructing Lua patterns, a form of [regular expression], see: * http://www.lua.org/manual/5.1/manual.html#5.4.1 * http://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#Patterns * http://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#Ustring_patterns ]] -- This sub-routine is exported for use in other modules function str._match( s, pattern, start, match_index, plain_flag, nomatch ) if s == '' then return str._error( 'Target string is empty' ) end if pattern == '' then return str._error( 'Pattern string is empty' ) end start = tonumber(start) or 1 if math.abs(start) < 1 or math.abs(start) > mw.ustring.len( s ) then return str._error( 'Requested start is out of range' ) end if match_index == 0 then return str._error( 'Match index is out of range' ) end if plain_flag then pattern = str._escapePattern( pattern ) end local result if match_index == 1 then -- Find first match is simple case result = mw.ustring.match( s, pattern, start ) else if start > 1 then s = mw.ustring.sub( s, start ) end local iterator = mw.ustring.gmatch(s, pattern) if match_index > 0 then -- Forward search for w in iterator do match_index = match_index - 1 if match_index == 0 then result = w break end end else -- Reverse search local result_table = {} local count = 1 for w in iterator do result_table[count] = w count = count + 1 end result = result_table[ count + match_index ] end end if result == nil then if nomatch == nil then return str._error( 'Match not found' ) else return nomatch end else return result end end --[[ match This function returns a substring from the source string that matches a specified pattern. Usage: {{#invoke:String|match|source_string|pattern_string|start_index|match_number|plain_flag|nomatch_output}} OR {{#invoke:String|match|s=source_string|pattern=pattern_string|start=start_index |match=match_number|plain=plain_flag|nomatch=nomatch_output}} Parameters s: The string to search pattern: The pattern or string to find within the string start: The index within the source string to start the search. The first character of the string has index 1. Defaults to 1. match: In some cases it may be possible to make multiple matches on a single string. This specifies which match to return, where the first match is match= 1. If a negative number is specified then a match is returned counting from the last match. Hence match = -1 is the same as requesting the last match. Defaults to 1. plain: A flag indicating that the pattern should be understood as plain text. Defaults to false. nomatch: If no match is found, output the "nomatch" value rather than an error. If invoked using named parameters, Mediawiki will automatically remove any leading or trailing whitespace from each string. In some circumstances this is desirable, in other cases one may want to preserve the whitespace. If the match_number or start_index are out of range for the string being queried, then this function generates an error. An error is also generated if no match is found. If one adds the parameter ignore_errors=true, then the error will be suppressed and an empty string will be returned on any failure. For information on constructing Lua patterns, a form of [regular expression], see: * http://www.lua.org/manual/5.1/manual.html#5.4.1 * http://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#Patterns * http://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#Ustring_patterns ]] -- This is the entry point for #invoke:String|match function str.match( frame ) local new_args = str._getParameters( frame.args, {'s', 'pattern', 'start', 'match', 'plain', 'nomatch'} ) local s = new_args['s'] or '' local start = tonumber( new_args['start'] ) or 1 local plain_flag = str._getBoolean( new_args['plain'] or false ) local pattern = new_args['pattern'] or '' local match_index = math.floor( tonumber(new_args['match']) or 1 ) local nomatch = new_args['nomatch'] return str._match( s, pattern, start, match_index, plain_flag, nomatch ) end --[[ pos This function returns a single character from the target string at position pos. Usage: {{#invoke:String|pos|target_string|index_value}} OR {{#invoke:String|pos|target=target_string|pos=index_value}} Parameters target: The string to search pos: The index for the character to return If invoked using named parameters, Mediawiki will automatically remove any leading or trailing whitespace from the target string. In some circumstances this is desirable, in other cases one may want to preserve the whitespace. The first character has an index value of 1. If one requests a negative value, this function will select a character by counting backwards from the end of the string. In other words pos = -1 is the same as asking for the last character. A requested value of zero, or a value greater than the length of the string returns an error. ]] function str.pos( frame ) local new_args = str._getParameters( frame.args, {'target', 'pos'} ) local target_str = new_args['target'] or '' local pos = tonumber( new_args['pos'] ) or 0 if pos == 0 or math.abs(pos) > mw.ustring.len( target_str ) then return str._error( 'String index out of range' ) end return mw.ustring.sub( target_str, pos, pos ) end --[[ str_find This function duplicates the behavior of {{str_find}}, including all of its quirks. This is provided in order to support existing templates, but is NOT RECOMMENDED for new code and templates. New code is recommended to use the "find" function instead. Returns the first index in "source" that is a match to "target". Indexing is 1-based, and the function returns -1 if the "target" string is not present in "source". Important Note: If the "target" string is empty / missing, this function returns a value of "1", which is generally unexpected behavior, and must be accounted for separatetly. ]] function str.str_find( frame ) local new_args = str._getParameters( frame.args, {'source', 'target'} ) local source_str = new_args['source'] or '' local target_str = new_args['target'] or '' if target_str == '' then return 1 end local start = mw.ustring.find( source_str, target_str, 1, true ) if start == nil then start = -1 end return start end --[[ find This function allows one to search for a target string or pattern within another string. Usage: {{#invoke:String|find|source_str|target_string|start_index|plain_flag}} OR {{#invoke:String|find|source=source_str|target=target_str|start=start_index|plain=plain_flag}} Parameters source: The string to search target: The string or pattern to find within source start: The index within the source string to start the search, defaults to 1 plain: Boolean flag indicating that target should be understood as plain text and not as a Lua style regular expression, defaults to true If invoked using named parameters, Mediawiki will automatically remove any leading or trailing whitespace from the parameter. In some circumstances this is desirable, in other cases one may want to preserve the whitespace. This function returns the first index >= "start" where "target" can be found within "source". Indices are 1-based. If "target" is not found, then this function returns 0. If either "source" or "target" are missing / empty, this function also returns 0. This function should be safe for UTF-8 strings. ]] function str.find( frame ) local new_args = str._getParameters( frame.args, {'source', 'target', 'start', 'plain' } ) local source_str = new_args['source'] or '' local pattern = new_args['target'] or '' local start_pos = tonumber(new_args['start']) or 1 local plain = new_args['plain'] or true if source_str == '' or pattern == '' then return 0 end plain = str._getBoolean( plain ) local start = mw.ustring.find( source_str, pattern, start_pos, plain ) if start == nil then start = 0 end return start end --[[ replace This function allows one to replace a target string or pattern within another string. Usage: {{#invoke:String|replace|source_str|pattern_string|replace_string|replacement_count|plain_flag}} OR {{#invoke:String|replace|source=source_string|pattern=pattern_string|replace=replace_string| count=replacement_count|plain=plain_flag}} Parameters source: The string to search pattern: The string or pattern to find within source replace: The replacement text count: The number of occurences to replace, defaults to all. plain: Boolean flag indicating that pattern should be understood as plain text and not as a Lua style regular expression, defaults to true ]] function str.replace( frame ) local new_args = str._getParameters( frame.args, {'source', 'pattern', 'replace', 'count', 'plain' } ) local source_str = new_args['source'] or '' local pattern = new_args['pattern'] or '' local replace = new_args['replace'] or '' local count = tonumber( new_args['count'] ) local plain = new_args['plain'] or true if source_str == '' or pattern == '' then return source_str end plain = str._getBoolean( plain ) if plain then pattern = str._escapePattern( pattern ) replace = string.gsub( replace, "%%", "%%%%" ) --Only need to escape replacement sequences. end local result if count ~= nil then result = mw.ustring.gsub( source_str, pattern, replace, count ) else result = mw.ustring.gsub( source_str, pattern, replace ) end return result end --[[ simple function to pipe string.rep to templates. ]] function str.rep( frame ) local repetitions = tonumber( frame.args[2] ) if not repetitions then return str._error( 'function rep expects a number as second parameter, received "' .. ( frame.args[2] or '' ) .. '"' ) end return string.rep( frame.args[1] or '', repetitions ) end --[[ escapePattern This function escapes special characters from a Lua string pattern. See [1] for details on how patterns work. [1] https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#Patterns Usage: {{#invoke:String|escapePattern|pattern_string}} Parameters pattern_string: The pattern string to escape. ]] function str.escapePattern( frame ) local pattern_str = frame.args[1] if not pattern_str then return str._error( 'No pattern string specified' ) end local result = str._escapePattern( pattern_str ) return result end --[[ count This function counts the number of occurrences of one string in another. ]] function str.count(frame) local args = str._getParameters(frame.args, {'source', 'pattern', 'plain'}) local source = args.source or '' local pattern = args.pattern or '' local plain = str._getBoolean(args.plain or true) if plain then pattern = str._escapePattern(pattern) end local _, count = mw.ustring.gsub(source, pattern, '') return count end --[[ endswith This function determines whether a string ends with another string. ]] function str.endswith(frame) local args = str._getParameters(frame.args, {'source', 'pattern'}) local source = args.source or '' local pattern = args.pattern or '' if pattern == '' then -- All strings end with the empty string. return "yes" end if mw.ustring.sub(source, -mw.ustring.len(pattern), -1) == pattern then return "yes" else return "" end end --[[ join Join all non empty arguments together; the first argument is the separator. Usage: {{#invoke:String|join|sep|one|two|three}} ]] function str.join(frame) local args = {} local sep for _, v in ipairs( frame.args ) do if sep then if v ~= '' then table.insert(args, v) end else sep = v end end return table.concat( args, sep or '' ) end -- findpagetext returns the position of a piece of text in a page -- First positional parameter or |text is the search text -- Optional parameter |title is the page title, defaults to current page -- Optional parameter |plain is either true for plain search (default) or false for Lua pattern search -- Optional parameter |nomatch is the return value when no match is found; default is nil function str._findpagetext(args) -- process parameters local nomatch = args.nomatch or "" if nomatch == "" then nomatch = nil end -- local text = mw.text.trim(args[1] or args.text or "") if text == "" then return nil end -- local title = args.title or "" local titleobj if title == "" then titleobj = mw.title.getCurrentTitle() else titleobj = mw.title.new(title) end -- local plain = args.plain or "" if plain:sub(1, 1) == "f" then plain = false else plain = true end -- get the page content and look for 'text' - return position or nomatch local content = titleobj and titleobj:getContent() return content and mw.ustring.find(content, text, 1, plain) or nomatch end function str.findpagetext(frame) local args = frame.args local pargs = frame:getParent().args for k, v in pairs(pargs) do args[k] = v end if not (args[1] or args.text) then return nil end -- just the first value return (str._findpagetext(args)) end --[[ Helper function that populates the argument list given that user may need to use a mix of named and unnamed parameters. This is relevant because named parameters are not identical to unnamed parameters due to string trimming, and when dealing with strings we sometimes want to either preserve or remove that whitespace depending on the application. ]] function str._getParameters( frame_args, arg_list ) local new_args = {} local index = 1 local value for _, arg in ipairs( arg_list ) do value = frame_args[arg] if value == nil then value = frame_args[index] index = index + 1 end new_args[arg] = value end return new_args end --[[ Helper function to handle error messages. ]] function str._error( error_str ) local frame = mw.getCurrentFrame() local error_category = frame.args.error_category or 'Errors reported by Module String' local ignore_errors = frame.args.ignore_errors or false local no_category = frame.args.no_category or false if str._getBoolean(ignore_errors) then return '' end local error_str = '<strong class="error">String Module Error: ' .. error_str .. '</strong>' if error_category ~= '' and not str._getBoolean( no_category ) then error_str = '[[Category:' .. error_category .. ']]' .. error_str end return error_str end --[[ Helper Function to interpret boolean strings ]] function str._getBoolean( boolean_str ) local boolean_value if type( boolean_str ) == 'string' then boolean_str = boolean_str:lower() if boolean_str == 'false' or boolean_str == 'no' or boolean_str == '0' or boolean_str == '' then boolean_value = false else boolean_value = true end elseif type( boolean_str ) == 'boolean' then boolean_value = boolean_str else error( 'No boolean value found' ) end return boolean_value end --[[ Helper function that escapes all pattern characters so that they will be treated as plain text. ]] function str._escapePattern( pattern_str ) return ( string.gsub( pattern_str, "[%(%)%.%%%+%-%*%?%[%^%$%]]", "%%%0" ) ) end return str j3b8d4sejqnvazpfl0gk0yfaa503gli 12 फरवरी 0 21682 803392 784501 2026-07-29T18:42:04Z SM7 3953 Restored revision 738926 by [[Special:Contributions/SM7Bot|SM7Bot]] ([[User talk:SM7Bot|talk]]): Rv / vand 803392 wikitext text/x-wiki {{कैलेंडर}} {{पिछला कुछ बरिस में ई दिन}} {{दिन}} ==घटना== ==जनम== * 1824 -दयानंद सरस्वती-एगो समाज सुधारक संत रहलन *1920 - '''प्राण''' - हिंदी सलीमा क एगो अभिनेता रहलन ==निधन == ==तिहुआर, छुट्टी अउरी खास महत्व == {{commons|February 12|12 फरवरी}} {{साल के महीना सभ}} [[श्रेणी:साल के दिन]] [[श्रेणी:फरवरी]] mnzttrzd2i2d5seuhdhb62eey7nhqq3 803394 803392 2026-07-29T18:46:07Z SM7 3953 सुधार कइल गइल 803394 wikitext text/x-wiki {{कैलेंडर}} {{पिछला कुछ बरिस में ई दिन}} {{दिन}} == घटना == == जनम == * [[1824]] — [[दयानंद सरस्वती]] — एगो समाज सुधारक संत रहलन * [[1920]] — '''[[प्राण (एक्टर)|प्राण]]''' — हिंदी सलीमा क एगो अभिनेता रहलन == निधन == == तिहुआर, छुट्टी अउरी खास महत्व == {{commons|February 12|12 फरवरी}} {{साल के महीना सभ}} [[श्रेणी:साल के दिन]] [[श्रेणी:फरवरी]] 1yvd2qj3rpbn9zanymlvrmccknifngz बर्की के लड़ाई 0 26192 803306 782048 2026-07-29T16:07:16Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803306 wikitext text/x-wiki {{Infobox military conflict |conflict =बर्की के लड़ाई |width = |partof =[[1965 के भारत-पाक युद्ध]] |image = |caption =ले॰ कर्नल [[हरी सिंह (सिपाही)|हरी सिंह]], भारतीय सेना के [[Indian 1st Armoured Division|18वीं कैवेलरी]], भारत के सेना द्वारा लाहौर जिला के बर्की पुलिस स्टेशन के बाहर फोट खिंचावत। |date =8-11 सितंबर 1965 के भइल<ref name="Major_1965">{{cite book |last = सिंह |first = ग्रुप कैप्टन रणबीर |title = Major defence operations since 1947 |publisher = Ocean Books |isbn = 81-88322-67-9}}</ref><ref name="Gupta_1965">{{cite book |last = Gupta |first = Hari Ram |title = India-Pakistan war, 1965, Volume 1 |publisher = Hariyana Prakashan, 1967 |pages = 154–157 |isbn = }}</ref> |place =[[बर्की, पाकिस्तान|बर्की]], [[लाहौर]], [[पाकिस्तान]] |coordinates ={{coord |31|2|52|N|74|5|6|E|display=inline,title}} |map_type = |map_relief = |latitude = |longitude = |map_size = |map_marksize = |map_caption = |map_label = |territory = |result =भारत के निर्णायक बिजय<ref name="sainik">{{cite web |url= http://sainiksamachar.nic.in/englisharchives/2009/jan15-09/h61.html|title= Battle of Burki was another outstanding infantry operation|author= Melville de Mellow|date= 28 November 1965|work= |publisher= Sainik Samachar|accessdate=}}</ref><ref name="Singh_1965"/><br> *लाहौर के ओर पाकिस्तानी टैंक हमला अउरी जवाबी हमला के असफलता <ref name="sainik">{{cite web |url= http://sainiksamachar.nic.in/englisharchives/2009/jan15-09/h61.html|title= Battle of Burki was another outstanding infantry operation|author= Melville de Mellow|date= 28 November 1965|work= |publisher= Sainik Samachar|accessdate=}}</ref><ref name="Singh_1965"/><ref>{{cite book |last = Singh |first = Gp Capt Ranbir |title = Memorable War Stories |publisher = Ocean Books |isbn = }}</ref> |status = |combatants_header = |combatant1 =<center>{{flagicon|India|size=65px}}<br>[[भारत]]</center> |combatant2 =<center>{{flagicon|Pakistan|size=65px}}<br>[[Pakistan]]</center> |commander1 ={{flagicon|India}} हर कृषण सिब्बल के बा<br>{{flagicon|India}} अनंत सिंह के ह |commander2 ={{flagicon|Pakistan}} [[Raja Aziz Bhatti]] {{KIA}} |units1 = |units2 = |strength1 =1 infantry division<ref name="Major_1965"/><br> 1 Armor Regiment<ref name="Chand_1965">{{cite book |last = Das |first = Chand N. |title = Hours of Glory: Famous Battles of the Indian army, 1801–1971 |publisher = Vision Books, 1997 |isbn = }}</ref> |strength2 =150 tanks<ref name="sainik"/>(3 Regiments)<br>2 Companies of 17 Punjab Regiment<br> 10 F-86 Sabre jets |casualties1 =4 tanks<ref name="sainik"/> |casualties2 =84 tanks<ref name="sainik"/> |notes = |campaignbox ={{Campaignbox Indo-Pakistani War of 1965}}<br>{{Campaignbox Indo-Pakistani Wars}} }} '''बर्की के लड़ाई''' भारत आ पाकिस्तान के सेना के बीच लड़ल गइल [[1965 के भारत-पाक युद्ध|1965 के युद्ध]]के हिस्सा रहल।<ref name="sainik"/> [[बर्की, पाकिस्तान|बर्की]] <small>({{Langx|ur|{{Nastaliq|'''بركى'''}}}})</small> पाकिस्तान में [[लाहौर]] के {{convert|6|mi|km|sp=us}} दक्खिन-पूरुब में भारत-पाकिस्तान बार्डर के नजदीक जगह बाटे<ref name="Ram_1965">{{cite book |last = Gopal |first = Ram |title = Indo-Pakistan War and Peace, 1965 |publisher = Pustak Kendra, 1967 |page = 118 |isbn = }}</ref> आ ई लाहौर से [[इच्छोगिल नहर]] के पुल द्वारा जुड़ल बाटे। लड़ाई के समय दुनों देस के सेना के संख्या लगभग बराबर रहे। पाकिस्तानी सैनिक नहर के किनारे खाई खन के ओह में से लड़त रहलें आ टैंक आ फाइटर जेट के सपोर्ट भी रहे।<ref name="sainik"/> भारतीय सेना 11 सितंबर 1965 के एह जगह के जीत लिहलस।<ref>{{cite news|last1=फज़ल|first1=रेहान|title=बर्की की वो घमासान लड़ाई|url=http://www.bbc.com/hindi/multimedia/2015/09/150911_1965_war_part11_vr|accessdate=11 जनवरी 2016|publisher=बीबीसी हिंदी|date=11 सितंबर 2015|language=hi}}</ref> ==बैकग्राउंड== पाकिस्तान 17 अगस्त 1965 के ऑपरेशन ग्रांड स्लैम शुरू कइलस, एह कोशिश में कि 15 अगस्त के ऑपरेशन जिब्राल्टर के असफलता के बाद घेराइल घुसपैठियन के राहत दिहल जा सके आ भारतीय आपूर्ति लाइन काट के कोसिस कइल गइल।<ref name="global">{{cite web |url= http://www.globalsecurity.org/military/world/war/indo-pak_1965.htm|title= Indo-Pakistan War of 1965|author= |work= |publisher= globalsecurity.org|accessdate=}}</ref> ऑपरेशन ग्रांड स्लैम के चलते आपूर्ति लाइन प बहुत तनाव के चलते भारत युद्ध में दूसरा मोर्चा खोले अउरी [[कश्मीर]] से पाकिस्तान के ध्यान भटकावे खाती लाहौर के ओर हमला शुरू कइलस।<ref name="global"/> लाहौर मोर्चा खोलला के बाद भारतीय सेना तीन गो कुल्हाड़ी-अमृतसर-लाहौर, खालरा-बुर्की-लाहौर आ खेम करण-कसूर सड़कन के साथ लाहौर के ओर बढ़ल आ छोट पाकिस्तानी सेना पर भारी पड़ल।<ref name="Johri_1965">{{cite book |last = Johri |first = Sitaram |title = The Indo-Pak conflict of 1965 |publisher = Himalaya Publications, 1967 |pages = 129–130 |isbn = }}</ref> एकमात्र भारतीय बख्तरबंद डिवीजन के समर्थन से भारतीय पैदल सेना जल्दी से अप्रस्तुत पाकिस्तानी रक्षकन के पीछे धकेल दिहलस जवना के मकसद लाहौर के घेरे आ संभवतः घेराबंदी कइल रहे| आश्चर्य के तत्व के चलते भारत भारत के सीमावर्ती शहर खलरा शहर से पाकिस्तानी इलाका के भारी मात्रा प कब्जा करे में कामयाब भईल जवन कि बुर्की से होके लाहौर जाए वाला सीधा सड़क प पड़ेला।<ref name="Gupta_1965"/><ref name="sainik"/> एह बीच पाकिस्तानी सेना एह इलाका में सैनिकन के जुटा लिहलसि आ तीन गो जवाबी हमला क के खोवल जमीन के फेर से कब्जा कर लिहलसि. एकरा बाद बुर्की के लड़ाई खलरा-बुर्की- लाहौर सड़क पर भइल।<ref name="sainik"/><ref name="Johri_1965"/> पाकिस्तान के मुख्य लक्ष्य रहे कि भारतीय पैदल सेना के बख्तरबंद सपोर्ट अउरी सप्लाई लाइन के पकड़े से पहिले पीछे हटे खातिर मजबूर कइल जाव| पाकिस्तानी सेना के मकसद भी रहे कि उ लड़ाई में पहिले जवन इलाका गंवा चुकल रहे ओकर बहुत हिस्सा प कब्जा कइल जाए|<ref name="Gupta_1965"/> भारतीय पैदल सेना के मकसद रहे कि जब तक कवच आ आपूर्ति समेत सुदृढीकरण ना आ जाव तबले बुर्की शहर पर कब्जा कर के ओकरा के पकड़ल जाव।<ref name="Major_1965"/><ref name="Gupta_1965"/> ==संदर्भ== {{reflist}} <!-- {{Military of India}} {{Military of Pakistan}} --> {{भारत-इति-आधार}} {{पाकिस्तान-आधार}} [[श्रेणी:भारत–पाकिस्तान संबंध]] [[श्रेणी:पाकिस्तान के इतिहास]] [[श्रेणी:भारत के इतिहास]] bn0g3nmjg2chrl2olhovz5bk476cv7t Module:Infobox 828 27376 803415 298492 2017-02-16T04:05:01Z en>Mr. Stradivarius 0 Changed protection level for "[[Module:Infobox]]": [[WP:High-risk templates|High-risk Lua module]]: allow template editors ([Edit=Require template editor access] (indefinite) [Move=Require template editor access] (indefinite)) 298492 Scribunto text/plain -- -- This module implements {{Infobox}} -- local p = {} local navbar = require('Module:Navbar')._navbar local args = {} local origArgs local root local function union(t1, t2) -- Returns the union of the values of two tables, as a sequence. local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end local function getArgNums(prefix) -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end local function addRow(rowArgs) -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. if rowArgs.header then root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :attr('id', rowArgs.rowid) :tag('th') :attr('colspan', 2) :attr('id', rowArgs.headerid) :addClass(rowArgs.class) :addClass(args.headerclass) :css('text-align', 'center') :cssText(args.headerstyle) :wikitext(rowArgs.header) elseif rowArgs.data then local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) row:attr('id', rowArgs.rowid) if rowArgs.label then row :tag('th') :attr('scope', 'row') :attr('id', rowArgs.labelid) :cssText(args.labelstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') if not rowArgs.label then dataCell :attr('colspan', 2) :css('text-align', 'center') end dataCell :attr('id', rowArgs.dataid) :addClass(rowArgs.class) :cssText(rowArgs.datastyle) :newline() :wikitext(rowArgs.data) end end local function renderTitle() if not args.title then return end root :tag('caption') :addClass(args.titleclass) :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end root :tag('tr') :tag('th') :attr('colspan', 2) :addClass(args.aboveclass) :css('text-align', 'center') :css('font-size', '125%') :css('font-weight', 'bold') :cssText(args.abovestyle) :wikitext(args.above) end local function renderBelowRow() if not args.below then return end root :tag('tr') :tag('td') :attr('colspan', '2') :addClass(args.belowclass) :css('text-align', 'center') :cssText(args.belowstyle) :newline() :wikitext(args.below) end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addRow({ data = args['subheader' .. tostring(num)], datastyle = args.subheaderstyle or args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :cssText(args.captionstyle) :wikitext(caption) end addRow({ data = tostring(data), datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end local function renderRows() -- Gets the union of the header and data argument numbers, -- and renders them all in order using addRow. local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], rowstyle = args['rowstyle' .. tostring(num)], dataid = args['dataid' .. tostring(num)], labelid = args['labelid' .. tostring(num)], headerid = args['headerid' .. tostring(num)], rowid = args['rowid' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end root :tag('tr') :tag('td') :attr('colspan', '2') :css('text-align', 'right') :wikitext(navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end local function renderTrackingCategories() if args.decat ~= 'yes' then if #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') end if args.child == 'yes' and args.title then root:wikitext('[[Category:Pages which use embedded infobox templates with the title parameter]]') end end end local function _infobox() -- Specify the overall layout of the infobox, with special settings -- if the infobox is used as a 'child' inside another infobox. if args.child ~= 'yes' then root = mw.html.create('table') root :addClass('infobox') :addClass(args.bodyclass) if args.subbox == 'yes' then root :css('padding', '0') :css('border', 'none') :css('margin', '-3px') :css('width', 'auto') :css('min-width', '100%') :css('font-size', '100%') :css('clear', 'none') :css('float', 'none') :css('background-color', 'transparent') else root :css('width', '22em') end root :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end renderSubheaders() renderImages() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderTrackingCategories() return tostring(root) end local function preprocessSingleArg(argName) -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end local function preprocessArgs(prefixTable, step) -- Assign the parameters with the given prefixes to the args table, in order, in batches -- of the step size specified. This is to prevent references etc. from appearing in the -- wrong order. The prefixTable should be an array containing tables, each of which has -- two possible fields, a "prefix" string and a "depend" table. The function always parses -- parameters containing the "prefix" string, but only parses parameters in the "depend" -- table if the prefix parameter is present and non-blank. if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then moreArgumentsExist = true -- Do another loop if any arguments are found, even blank ones. preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present and not blank, or -- we are processing "prefix1" and "prefix" is present and not blank, and -- if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end function p.infobox(frame) -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end -- Parse the data parameters in the same order that the old {{infobox}} did, so that -- references etc. will display in the expected places. Parameters that depend on -- another parameter are only processed if that parameter is present, to avoid -- phantom references appearing in article reference lists. preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'class'}, {prefix = 'dataid'}, {prefix = 'labelid'}, {prefix = 'headerid'}, {prefix = 'rowid'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') args['italic title'] = origArgs['italic title'] -- different behaviour if blank or absent preprocessSingleArg('decat') return _infobox() end return p 6706hcou5akn7nrr69xksoq3fnha2yw 803416 803415 2017-04-04T05:39:21Z en>Frietjes 0 rowcellstyle per talk page 803416 Scribunto text/plain -- -- This module implements {{Infobox}} -- local p = {} local navbar = require('Module:Navbar')._navbar local args = {} local origArgs local root local function union(t1, t2) -- Returns the union of the values of two tables, as a sequence. local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end local function getArgNums(prefix) -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end local function addRow(rowArgs) -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. if rowArgs.header then root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :attr('id', rowArgs.rowid) :tag('th') :attr('colspan', 2) :attr('id', rowArgs.headerid) :addClass(rowArgs.class) :addClass(args.headerclass) :css('text-align', 'center') :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.header) elseif rowArgs.data then local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) row:attr('id', rowArgs.rowid) if rowArgs.label then row :tag('th') :attr('scope', 'row') :attr('id', rowArgs.labelid) :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') if not rowArgs.label then dataCell :attr('colspan', 2) :css('text-align', 'center') end dataCell :attr('id', rowArgs.dataid) :addClass(rowArgs.class) :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :newline() :wikitext(rowArgs.data) end end local function renderTitle() if not args.title then return end root :tag('caption') :addClass(args.titleclass) :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end root :tag('tr') :tag('th') :attr('colspan', 2) :addClass(args.aboveclass) :css('text-align', 'center') :css('font-size', '125%') :css('font-weight', 'bold') :cssText(args.abovestyle) :wikitext(args.above) end local function renderBelowRow() if not args.below then return end root :tag('tr') :tag('td') :attr('colspan', '2') :addClass(args.belowclass) :css('text-align', 'center') :cssText(args.belowstyle) :newline() :wikitext(args.below) end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addRow({ data = args['subheader' .. tostring(num)], datastyle = args.subheaderstyle or args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :cssText(args.captionstyle) :wikitext(caption) end addRow({ data = tostring(data), datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end local function renderRows() -- Gets the union of the header and data argument numbers, -- and renders them all in order using addRow. local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)], dataid = args['dataid' .. tostring(num)], labelid = args['labelid' .. tostring(num)], headerid = args['headerid' .. tostring(num)], rowid = args['rowid' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end root :tag('tr') :tag('td') :attr('colspan', '2') :css('text-align', 'right') :wikitext(navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end local function renderTrackingCategories() if args.decat ~= 'yes' then if #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') end if args.child == 'yes' and args.title then root:wikitext('[[Category:Pages which use embedded infobox templates with the title parameter]]') end end end local function _infobox() -- Specify the overall layout of the infobox, with special settings -- if the infobox is used as a 'child' inside another infobox. if args.child ~= 'yes' then root = mw.html.create('table') root :addClass('infobox') :addClass(args.bodyclass) if args.subbox == 'yes' then root :css('padding', '0') :css('border', 'none') :css('margin', '-3px') :css('width', 'auto') :css('min-width', '100%') :css('font-size', '100%') :css('clear', 'none') :css('float', 'none') :css('background-color', 'transparent') else root :css('width', '22em') end root :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end renderSubheaders() renderImages() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderTrackingCategories() return tostring(root) end local function preprocessSingleArg(argName) -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end local function preprocessArgs(prefixTable, step) -- Assign the parameters with the given prefixes to the args table, in order, in batches -- of the step size specified. This is to prevent references etc. from appearing in the -- wrong order. The prefixTable should be an array containing tables, each of which has -- two possible fields, a "prefix" string and a "depend" table. The function always parses -- parameters containing the "prefix" string, but only parses parameters in the "depend" -- table if the prefix parameter is present and non-blank. if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then moreArgumentsExist = true -- Do another loop if any arguments are found, even blank ones. preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present and not blank, or -- we are processing "prefix1" and "prefix" is present and not blank, and -- if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end function p.infobox(frame) -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end -- Parse the data parameters in the same order that the old {{infobox}} did, so that -- references etc. will display in the expected places. Parameters that depend on -- another parameter are only processed if that parameter is present, to avoid -- phantom references appearing in article reference lists. preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'}, {prefix = 'dataid'}, {prefix = 'labelid'}, {prefix = 'headerid'}, {prefix = 'rowid'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') args['italic title'] = origArgs['italic title'] -- different behaviour if blank or absent preprocessSingleArg('decat') return _infobox() end return p edprki3cbc91qzciqck4g7yuj9k0n18 803417 803416 2017-05-29T13:38:50Z en>Jc86035 0 fixes for child infoboxes so HTML Tidy is not required 803417 Scribunto text/plain -- -- This module implements {{Infobox}} -- local p = {} local navbar = require('Module:Navbar')._navbar local args = {} local origArgs local root local orphancat = 'Category:Orphan infoboxes' local function notempty( s ) return s and s:match( '%S' ) end local function union(t1, t2) -- Returns the union of the values of two tables, as a sequence. local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end local function getArgNums(prefix) -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end local function addRow(rowArgs) -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. local s = {} local ttype = 'td' if rowArgs.header then rowArgs.data = nil s = mw.text.split(rowArgs.header, '%[%[' .. orphancat .. '%]%]') if notempty(s[1]) then rowArgs.header = s[1] else rowArgs.header = nil end ttype = 'th' elseif rowArgs.data then s = mw.text.split(rowArgs.data, '%[%[' .. orphancat .. '%]%]') if notempty(s[1]) then rowArgs.data = s[1] else rowArgs.data = nil end ttype = 'td' end if rowArgs.header then root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :attr('id', rowArgs.rowid) :tag('th') :attr('colspan', 2) :attr('id', rowArgs.headerid) :addClass(rowArgs.class) :addClass(args.headerclass) :css('text-align', 'center') :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.header) elseif rowArgs.data then local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) row:attr('id', rowArgs.rowid) if rowArgs.label then row :tag('th') :attr('scope', 'row') :attr('id', rowArgs.labelid) :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local s = mw.text.split(rowArgs.data, '%[%[' .. orphancat .. '%]%]') rowArgs.data = s[1] local dataCell = row:tag('td') if not rowArgs.label then dataCell :attr('colspan', 2) :css('text-align', 'center') end dataCell :attr('id', rowArgs.dataid) :addClass(rowArgs.class) :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :newline() :wikitext(rowArgs.data) end for i = 2, #s do if notempty(s[i]) then if (i % 2) == 0 then root :wikitext(s[i]) else local dataCell = root:tag('tr'):tag(ttype) dataCell :attr('colspan', 2) :wikitext(s[i]) end end end end local function renderTitle() if not args.title then return end root :tag('caption') :addClass(args.titleclass) :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end root :tag('tr') :tag('th') :attr('colspan', 2) :addClass(args.aboveclass) :css('text-align', 'center') :css('font-size', '125%') :css('font-weight', 'bold') :cssText(args.abovestyle) :wikitext(args.above) end local function renderBelowRow() if not args.below then return end root :tag('tr') :tag('td') :attr('colspan', '2') :addClass(args.belowclass) :css('text-align', 'center') :cssText(args.belowstyle) :newline() :wikitext(args.below) end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addRow({ data = args['subheader' .. tostring(num)], datastyle = args.subheaderstyle or args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :cssText(args.captionstyle) :wikitext(caption) end addRow({ data = tostring(data), datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end local function renderRows() -- Gets the union of the header and data argument numbers, -- and renders them all in order using addRow. local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)], dataid = args['dataid' .. tostring(num)], labelid = args['labelid' .. tostring(num)], headerid = args['headerid' .. tostring(num)], rowid = args['rowid' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end root :tag('tr') :tag('td') :attr('colspan', '2') :css('text-align', 'right') :wikitext(navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end local function renderTrackingCategories() if args.decat ~= 'yes' then if #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') end if args.child == 'yes' and args.title then root:wikitext('[[Category:Pages which use embedded infobox templates with the title parameter]]') end end end local function _infobox() -- Specify the overall layout of the infobox, with special settings -- if the infobox is used as a 'child' inside another infobox. local marker = '' if args.child ~= 'yes' then root = mw.html.create('table') root :addClass('infobox') :addClass(args.bodyclass) if args.subbox == 'yes' then root :css('padding', '0') :css('border', 'none') :css('margin', '-3px') :css('width', 'auto') :css('min-width', '100%') :css('font-size', '100%') :css('clear', 'none') :css('float', 'none') :css('background-color', 'transparent') else root :css('width', '22em') end root :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() marker = '[[' .. orphancat .. ']]' root :wikitext(args.title) end renderSubheaders() renderImages() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderTrackingCategories() return marker .. tostring(root) .. marker end local function preprocessSingleArg(argName) -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end local function preprocessArgs(prefixTable, step) -- Assign the parameters with the given prefixes to the args table, in order, in batches -- of the step size specified. This is to prevent references etc. from appearing in the -- wrong order. The prefixTable should be an array containing tables, each of which has -- two possible fields, a "prefix" string and a "depend" table. The function always parses -- parameters containing the "prefix" string, but only parses parameters in the "depend" -- table if the prefix parameter is present and non-blank. if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then moreArgumentsExist = true -- Do another loop if any arguments are found, even blank ones. preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present and not blank, or -- we are processing "prefix1" and "prefix" is present and not blank, and -- if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end function p.infobox(frame) -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end -- Parse the data parameters in the same order that the old {{infobox}} did, so that -- references etc. will display in the expected places. Parameters that depend on -- another parameter are only processed if that parameter is present, to avoid -- phantom references appearing in article reference lists. preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'}, {prefix = 'dataid'}, {prefix = 'labelid'}, {prefix = 'headerid'}, {prefix = 'rowid'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') args['italic title'] = origArgs['italic title'] -- different behaviour if blank or absent preprocessSingleArg('decat') return _infobox() end return p lq21lj4acq8hq4095hlnt7ceyb0odt0 803418 803417 2017-05-29T14:12:49Z en>Frietjes 0 Undid revision 782833995 by [[Special:Contributions/Jc86035|Jc86035]] ([[User talk:Jc86035|talk]]) looks like this caused some serious problems, will require more testing (see, e.g, my talk page) 803418 Scribunto text/plain -- -- This module implements {{Infobox}} -- local p = {} local navbar = require('Module:Navbar')._navbar local args = {} local origArgs local root local function union(t1, t2) -- Returns the union of the values of two tables, as a sequence. local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end local function getArgNums(prefix) -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end local function addRow(rowArgs) -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. if rowArgs.header then root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :attr('id', rowArgs.rowid) :tag('th') :attr('colspan', 2) :attr('id', rowArgs.headerid) :addClass(rowArgs.class) :addClass(args.headerclass) :css('text-align', 'center') :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.header) elseif rowArgs.data then local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) row:attr('id', rowArgs.rowid) if rowArgs.label then row :tag('th') :attr('scope', 'row') :attr('id', rowArgs.labelid) :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') if not rowArgs.label then dataCell :attr('colspan', 2) :css('text-align', 'center') end dataCell :attr('id', rowArgs.dataid) :addClass(rowArgs.class) :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :newline() :wikitext(rowArgs.data) end end local function renderTitle() if not args.title then return end root :tag('caption') :addClass(args.titleclass) :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end root :tag('tr') :tag('th') :attr('colspan', 2) :addClass(args.aboveclass) :css('text-align', 'center') :css('font-size', '125%') :css('font-weight', 'bold') :cssText(args.abovestyle) :wikitext(args.above) end local function renderBelowRow() if not args.below then return end root :tag('tr') :tag('td') :attr('colspan', '2') :addClass(args.belowclass) :css('text-align', 'center') :cssText(args.belowstyle) :newline() :wikitext(args.below) end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addRow({ data = args['subheader' .. tostring(num)], datastyle = args.subheaderstyle or args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :cssText(args.captionstyle) :wikitext(caption) end addRow({ data = tostring(data), datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end local function renderRows() -- Gets the union of the header and data argument numbers, -- and renders them all in order using addRow. local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)], dataid = args['dataid' .. tostring(num)], labelid = args['labelid' .. tostring(num)], headerid = args['headerid' .. tostring(num)], rowid = args['rowid' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end root :tag('tr') :tag('td') :attr('colspan', '2') :css('text-align', 'right') :wikitext(navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end local function renderTrackingCategories() if args.decat ~= 'yes' then if #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') end if args.child == 'yes' and args.title then root:wikitext('[[Category:Pages which use embedded infobox templates with the title parameter]]') end end end local function _infobox() -- Specify the overall layout of the infobox, with special settings -- if the infobox is used as a 'child' inside another infobox. if args.child ~= 'yes' then root = mw.html.create('table') root :addClass('infobox') :addClass(args.bodyclass) if args.subbox == 'yes' then root :css('padding', '0') :css('border', 'none') :css('margin', '-3px') :css('width', 'auto') :css('min-width', '100%') :css('font-size', '100%') :css('clear', 'none') :css('float', 'none') :css('background-color', 'transparent') else root :css('width', '22em') end root :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end renderSubheaders() renderImages() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderTrackingCategories() return tostring(root) end local function preprocessSingleArg(argName) -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end local function preprocessArgs(prefixTable, step) -- Assign the parameters with the given prefixes to the args table, in order, in batches -- of the step size specified. This is to prevent references etc. from appearing in the -- wrong order. The prefixTable should be an array containing tables, each of which has -- two possible fields, a "prefix" string and a "depend" table. The function always parses -- parameters containing the "prefix" string, but only parses parameters in the "depend" -- table if the prefix parameter is present and non-blank. if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then moreArgumentsExist = true -- Do another loop if any arguments are found, even blank ones. preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present and not blank, or -- we are processing "prefix1" and "prefix" is present and not blank, and -- if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end function p.infobox(frame) -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end -- Parse the data parameters in the same order that the old {{infobox}} did, so that -- references etc. will display in the expected places. Parameters that depend on -- another parameter are only processed if that parameter is present, to avoid -- phantom references appearing in article reference lists. preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'}, {prefix = 'dataid'}, {prefix = 'labelid'}, {prefix = 'headerid'}, {prefix = 'rowid'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') args['italic title'] = origArgs['italic title'] -- different behaviour if blank or absent preprocessSingleArg('decat') return _infobox() end return p edprki3cbc91qzciqck4g7yuj9k0n18 803419 803418 2017-06-05T13:55:32Z en>Jc86035 0 merge from sandbox; still some pages with odd infoboxes over the place because of Tidy 803419 Scribunto text/plain -- -- This module implements {{Infobox}} -- local p = {} local navbar = require('Module:Navbar')._navbar local args = {} local origArgs local root local function notempty( s ) return s and s:match( '%S' ) end local function fixChildBoxes(sval, tt) if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end return s else return sval end end local function union(t1, t2) -- Returns the union of the values of two tables, as a sequence. local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end local function getArgNums(prefix) -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end local function addRow(rowArgs) -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. if rowArgs.header then root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :attr('id', rowArgs.rowid) :tag('th') :attr('colspan', 2) :attr('id', rowArgs.headerid) :addClass(rowArgs.class) :addClass(args.headerclass) :css('text-align', 'center') :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) elseif rowArgs.data then local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) row:attr('id', rowArgs.rowid) if rowArgs.label then row :tag('th') :attr('scope', 'row') :attr('id', rowArgs.labelid) :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') if not rowArgs.label then dataCell :attr('colspan', 2) :css('text-align', 'center') end dataCell :attr('id', rowArgs.dataid) :addClass(rowArgs.class) :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :newline() :wikitext(fixChildBoxes(rowArgs.data, 'td')) end end local function renderTitle() if not args.title then return end root :tag('caption') :addClass(args.titleclass) :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end root :tag('tr') :tag('th') :attr('colspan', 2) :addClass(args.aboveclass) :css('text-align', 'center') :css('font-size', '125%') :css('font-weight', 'bold') :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end root :tag('tr') :tag('td') :attr('colspan', '2') :addClass(args.belowclass) :css('text-align', 'center') :cssText(args.belowstyle) :newline() :wikitext(fixChildBoxes(args.below,'td')) end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addRow({ data = args['subheader' .. tostring(num)], datastyle = args.subheaderstyle or args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :cssText(args.captionstyle) :wikitext(caption) end addRow({ data = tostring(data), datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end local function renderRows() -- Gets the union of the header and data argument numbers, -- and renders them all in order using addRow. local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)], dataid = args['dataid' .. tostring(num)], labelid = args['labelid' .. tostring(num)], headerid = args['headerid' .. tostring(num)], rowid = args['rowid' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end root :tag('tr') :tag('td') :attr('colspan', '2') :css('text-align', 'right') :wikitext(navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end local function renderTrackingCategories() if args.decat ~= 'yes' then if #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') end if args.child == 'yes' and args.title then root:wikitext('[[Category:Pages which use embedded infobox templates with the title parameter]]') end end end local function _infobox() -- Specify the overall layout of the infobox, with special settings -- if the infobox is used as a 'child' inside another infobox. if args.child ~= 'yes' then root = mw.html.create('table') root :addClass('infobox') :addClass(args.bodyclass) if args.subbox == 'yes' then root :css('padding', '0') :css('border', 'none') :css('margin', '-3px') :css('width', 'auto') :css('min-width', '100%') :css('font-size', '100%') :css('clear', 'none') :css('float', 'none') :css('background-color', 'transparent') else root :css('width', '22em') end root :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end renderSubheaders() renderImages() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderTrackingCategories() return tostring(root) end local function preprocessSingleArg(argName) -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end local function preprocessArgs(prefixTable, step) -- Assign the parameters with the given prefixes to the args table, in order, in batches -- of the step size specified. This is to prevent references etc. from appearing in the -- wrong order. The prefixTable should be an array containing tables, each of which has -- two possible fields, a "prefix" string and a "depend" table. The function always parses -- parameters containing the "prefix" string, but only parses parameters in the "depend" -- table if the prefix parameter is present and non-blank. if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then moreArgumentsExist = true -- Do another loop if any arguments are found, even blank ones. preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present and not blank, or -- we are processing "prefix1" and "prefix" is present and not blank, and -- if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end function p.infobox(frame) -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end -- Parse the data parameters in the same order that the old {{infobox}} did, so that -- references etc. will display in the expected places. Parameters that depend on -- another parameter are only processed if that parameter is present, to avoid -- phantom references appearing in article reference lists. preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'}, {prefix = 'dataid'}, {prefix = 'labelid'}, {prefix = 'headerid'}, {prefix = 'rowid'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') args['italic title'] = origArgs['italic title'] -- different behaviour if blank or absent preprocessSingleArg('decat') return _infobox() end return p 36dop1z7whmr9cvip6xt71vste6ng2e 803420 803419 2017-06-07T12:14:38Z en>Jc86035 0 Undid revision 783927044 by [[Special:Contributions/Jc86035|Jc86035]] ([[User talk:Jc86035|talk]]) Doesn't play well with succession boxes 803420 Scribunto text/plain -- -- This module implements {{Infobox}} -- local p = {} local navbar = require('Module:Navbar')._navbar local args = {} local origArgs local root local function union(t1, t2) -- Returns the union of the values of two tables, as a sequence. local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end local function getArgNums(prefix) -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end local function addRow(rowArgs) -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. if rowArgs.header then root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :attr('id', rowArgs.rowid) :tag('th') :attr('colspan', 2) :attr('id', rowArgs.headerid) :addClass(rowArgs.class) :addClass(args.headerclass) :css('text-align', 'center') :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.header) elseif rowArgs.data then local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) row:attr('id', rowArgs.rowid) if rowArgs.label then row :tag('th') :attr('scope', 'row') :attr('id', rowArgs.labelid) :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') if not rowArgs.label then dataCell :attr('colspan', 2) :css('text-align', 'center') end dataCell :attr('id', rowArgs.dataid) :addClass(rowArgs.class) :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :newline() :wikitext(rowArgs.data) end end local function renderTitle() if not args.title then return end root :tag('caption') :addClass(args.titleclass) :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end root :tag('tr') :tag('th') :attr('colspan', 2) :addClass(args.aboveclass) :css('text-align', 'center') :css('font-size', '125%') :css('font-weight', 'bold') :cssText(args.abovestyle) :wikitext(args.above) end local function renderBelowRow() if not args.below then return end root :tag('tr') :tag('td') :attr('colspan', '2') :addClass(args.belowclass) :css('text-align', 'center') :cssText(args.belowstyle) :newline() :wikitext(args.below) end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addRow({ data = args['subheader' .. tostring(num)], datastyle = args.subheaderstyle or args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :cssText(args.captionstyle) :wikitext(caption) end addRow({ data = tostring(data), datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end local function renderRows() -- Gets the union of the header and data argument numbers, -- and renders them all in order using addRow. local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)], dataid = args['dataid' .. tostring(num)], labelid = args['labelid' .. tostring(num)], headerid = args['headerid' .. tostring(num)], rowid = args['rowid' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end root :tag('tr') :tag('td') :attr('colspan', '2') :css('text-align', 'right') :wikitext(navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end local function renderTrackingCategories() if args.decat ~= 'yes' then if #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') end if args.child == 'yes' and args.title then root:wikitext('[[Category:Pages which use embedded infobox templates with the title parameter]]') end end end local function _infobox() -- Specify the overall layout of the infobox, with special settings -- if the infobox is used as a 'child' inside another infobox. if args.child ~= 'yes' then root = mw.html.create('table') root :addClass('infobox') :addClass(args.bodyclass) if args.subbox == 'yes' then root :css('padding', '0') :css('border', 'none') :css('margin', '-3px') :css('width', 'auto') :css('min-width', '100%') :css('font-size', '100%') :css('clear', 'none') :css('float', 'none') :css('background-color', 'transparent') else root :css('width', '22em') end root :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end renderSubheaders() renderImages() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderTrackingCategories() return tostring(root) end local function preprocessSingleArg(argName) -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end local function preprocessArgs(prefixTable, step) -- Assign the parameters with the given prefixes to the args table, in order, in batches -- of the step size specified. This is to prevent references etc. from appearing in the -- wrong order. The prefixTable should be an array containing tables, each of which has -- two possible fields, a "prefix" string and a "depend" table. The function always parses -- parameters containing the "prefix" string, but only parses parameters in the "depend" -- table if the prefix parameter is present and non-blank. if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then moreArgumentsExist = true -- Do another loop if any arguments are found, even blank ones. preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present and not blank, or -- we are processing "prefix1" and "prefix" is present and not blank, and -- if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end function p.infobox(frame) -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end -- Parse the data parameters in the same order that the old {{infobox}} did, so that -- references etc. will display in the expected places. Parameters that depend on -- another parameter are only processed if that parameter is present, to avoid -- phantom references appearing in article reference lists. preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'}, {prefix = 'dataid'}, {prefix = 'labelid'}, {prefix = 'headerid'}, {prefix = 'rowid'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') args['italic title'] = origArgs['italic title'] -- different behaviour if blank or absent preprocessSingleArg('decat') return _infobox() end return p edprki3cbc91qzciqck4g7yuj9k0n18 803421 803420 2017-06-17T07:53:56Z en>Jc86035 0 try implementing again? 803421 Scribunto text/plain -- -- This module implements {{Infobox}} -- local p = {} local navbar = require('Module:Navbar')._navbar local args = {} local origArgs local root local function notempty( s ) return s and s:match( '%S' ) end local function fixChildBoxes(sval, tt) if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end return s else return sval end end local function union(t1, t2) -- Returns the union of the values of two tables, as a sequence. local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end local function getArgNums(prefix) -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end local function addRow(rowArgs) -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. if rowArgs.header then root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :attr('id', rowArgs.rowid) :tag('th') :attr('colspan', 2) :attr('id', rowArgs.headerid) :addClass(rowArgs.class) :addClass(args.headerclass) :css('text-align', 'center') :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) elseif rowArgs.data then local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) row:attr('id', rowArgs.rowid) if rowArgs.label then row :tag('th') :attr('scope', 'row') :attr('id', rowArgs.labelid) :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') if not rowArgs.label then dataCell :attr('colspan', 2) :css('text-align', 'center') end dataCell :attr('id', rowArgs.dataid) :addClass(rowArgs.class) :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :newline() :wikitext(fixChildBoxes(rowArgs.data, 'td')) end end local function renderTitle() if not args.title then return end root :tag('caption') :addClass(args.titleclass) :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end root :tag('tr') :tag('th') :attr('colspan', 2) :addClass(args.aboveclass) :css('text-align', 'center') :css('font-size', '125%') :css('font-weight', 'bold') :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end root :tag('tr') :tag('td') :attr('colspan', '2') :addClass(args.belowclass) :css('text-align', 'center') :cssText(args.belowstyle) :newline() :wikitext(fixChildBoxes(args.below,'td')) end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addRow({ data = args['subheader' .. tostring(num)], datastyle = args.subheaderstyle or args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :cssText(args.captionstyle) :wikitext(caption) end addRow({ data = tostring(data), datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end local function renderRows() -- Gets the union of the header and data argument numbers, -- and renders them all in order using addRow. local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)], dataid = args['dataid' .. tostring(num)], labelid = args['labelid' .. tostring(num)], headerid = args['headerid' .. tostring(num)], rowid = args['rowid' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end root :tag('tr') :tag('td') :attr('colspan', '2') :css('text-align', 'right') :wikitext(navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end local function renderTrackingCategories() if args.decat ~= 'yes' then if #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') end if args.child == 'yes' and args.title then root:wikitext('[[Category:Pages which use embedded infobox templates with the title parameter]]') end end end local function _infobox() -- Specify the overall layout of the infobox, with special settings -- if the infobox is used as a 'child' inside another infobox. if args.child ~= 'yes' then root = mw.html.create('table') root :addClass('infobox') :addClass(args.bodyclass) if args.subbox == 'yes' then root :css('padding', '0') :css('border', 'none') :css('margin', '-3px') :css('width', 'auto') :css('min-width', '100%') :css('font-size', '100%') :css('clear', 'none') :css('float', 'none') :css('background-color', 'transparent') else root :css('width', '22em') end root :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end renderSubheaders() renderImages() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderTrackingCategories() return tostring(root) end local function preprocessSingleArg(argName) -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end local function preprocessArgs(prefixTable, step) -- Assign the parameters with the given prefixes to the args table, in order, in batches -- of the step size specified. This is to prevent references etc. from appearing in the -- wrong order. The prefixTable should be an array containing tables, each of which has -- two possible fields, a "prefix" string and a "depend" table. The function always parses -- parameters containing the "prefix" string, but only parses parameters in the "depend" -- table if the prefix parameter is present and non-blank. if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then moreArgumentsExist = true -- Do another loop if any arguments are found, even blank ones. preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present and not blank, or -- we are processing "prefix1" and "prefix" is present and not blank, and -- if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end function p.infobox(frame) -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end -- Parse the data parameters in the same order that the old {{infobox}} did, so that -- references etc. will display in the expected places. Parameters that depend on -- another parameter are only processed if that parameter is present, to avoid -- phantom references appearing in article reference lists. preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'}, {prefix = 'dataid'}, {prefix = 'labelid'}, {prefix = 'headerid'}, {prefix = 'rowid'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') args['italic title'] = origArgs['italic title'] -- different behaviour if blank or absent preprocessSingleArg('decat') return _infobox() end return p h7ia1migmxaed0jrzap3fnixrgg2049 803422 803421 2018-01-24T22:39:59Z en>Frietjes 0 infobox class not needed for subbox since the parent box will set the class (and causes problems on mobile) 803422 Scribunto text/plain -- -- This module implements {{Infobox}} -- local p = {} local navbar = require('Module:Navbar')._navbar local args = {} local origArgs local root local function notempty( s ) return s and s:match( '%S' ) end local function fixChildBoxes(sval, tt) if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end return s else return sval end end local function union(t1, t2) -- Returns the union of the values of two tables, as a sequence. local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end local function getArgNums(prefix) -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end local function addRow(rowArgs) -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. if rowArgs.header then root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :attr('id', rowArgs.rowid) :tag('th') :attr('colspan', 2) :attr('id', rowArgs.headerid) :addClass(rowArgs.class) :addClass(args.headerclass) :css('text-align', 'center') :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) elseif rowArgs.data then local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) row:attr('id', rowArgs.rowid) if rowArgs.label then row :tag('th') :attr('scope', 'row') :attr('id', rowArgs.labelid) :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') if not rowArgs.label then dataCell :attr('colspan', 2) :css('text-align', 'center') end dataCell :attr('id', rowArgs.dataid) :addClass(rowArgs.class) :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :newline() :wikitext(fixChildBoxes(rowArgs.data, 'td')) end end local function renderTitle() if not args.title then return end root :tag('caption') :addClass(args.titleclass) :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end root :tag('tr') :tag('th') :attr('colspan', 2) :addClass(args.aboveclass) :css('text-align', 'center') :css('font-size', '125%') :css('font-weight', 'bold') :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end root :tag('tr') :tag('td') :attr('colspan', '2') :addClass(args.belowclass) :css('text-align', 'center') :cssText(args.belowstyle) :newline() :wikitext(fixChildBoxes(args.below,'td')) end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addRow({ data = args['subheader' .. tostring(num)], datastyle = args.subheaderstyle or args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :cssText(args.captionstyle) :wikitext(caption) end addRow({ data = tostring(data), datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end local function renderRows() -- Gets the union of the header and data argument numbers, -- and renders them all in order using addRow. local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)], dataid = args['dataid' .. tostring(num)], labelid = args['labelid' .. tostring(num)], headerid = args['headerid' .. tostring(num)], rowid = args['rowid' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end root :tag('tr') :tag('td') :attr('colspan', '2') :css('text-align', 'right') :wikitext(navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end local function renderTrackingCategories() if args.decat ~= 'yes' then if #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') end if args.child == 'yes' and args.title then root:wikitext('[[Category:Pages which use embedded infobox templates with the title parameter]]') end end end local function _infobox() -- Specify the overall layout of the infobox, with special settings -- if the infobox is used as a 'child' inside another infobox. if args.child ~= 'yes' then root = mw.html.create('table') root :addClass((args.subbox ~= 'yes') and 'infobox' or nil) :addClass(args.bodyclass) if args.subbox == 'yes' then root :css('padding', '0') :css('border', 'none') :css('margin', '-3px') :css('width', 'auto') :css('min-width', '100%') :css('font-size', '100%') :css('clear', 'none') :css('float', 'none') :css('background-color', 'transparent') else root :css('width', '22em') end root :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end renderSubheaders() renderImages() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderTrackingCategories() return tostring(root) end local function preprocessSingleArg(argName) -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end local function preprocessArgs(prefixTable, step) -- Assign the parameters with the given prefixes to the args table, in order, in batches -- of the step size specified. This is to prevent references etc. from appearing in the -- wrong order. The prefixTable should be an array containing tables, each of which has -- two possible fields, a "prefix" string and a "depend" table. The function always parses -- parameters containing the "prefix" string, but only parses parameters in the "depend" -- table if the prefix parameter is present and non-blank. if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then moreArgumentsExist = true -- Do another loop if any arguments are found, even blank ones. preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present and not blank, or -- we are processing "prefix1" and "prefix" is present and not blank, and -- if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end function p.infobox(frame) -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end -- Parse the data parameters in the same order that the old {{infobox}} did, so that -- references etc. will display in the expected places. Parameters that depend on -- another parameter are only processed if that parameter is present, to avoid -- phantom references appearing in article reference lists. preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'}, {prefix = 'dataid'}, {prefix = 'labelid'}, {prefix = 'headerid'}, {prefix = 'rowid'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') args['italic title'] = origArgs['italic title'] -- different behaviour if blank or absent preprocessSingleArg('decat') return _infobox() end return p 70c4sw4coyjec1b3vfiyyrfzu3bapv8 803423 803422 2018-07-06T13:05:33Z en>Frietjes 0 add per https://en.wikipedia.org/w/index.php?title=Template_talk:Infobox_musical_artist&oldid=849054481 see "two plain list" testcase 803423 Scribunto text/plain -- -- This module implements {{Infobox}} -- local p = {} local navbar = require('Module:Navbar')._navbar local args = {} local origArgs local root local function notempty( s ) return s and s:match( '%S' ) end local function fixChildBoxes(sval, tt) if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end -- the next two lines add a newline at the end of * lists for the PHP parser -- https://en.wikipedia.org/w/index.php?title=Template_talk:Infobox_musical_artist&oldid=849054481 s = mw.ustring.gsub(s, '([\r\n]%*[^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^(%*[^\r\n]*)$', '%1\n') return s else return sval end end local function union(t1, t2) -- Returns the union of the values of two tables, as a sequence. local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end local function getArgNums(prefix) -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end local function addRow(rowArgs) -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. if rowArgs.header then root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :attr('id', rowArgs.rowid) :tag('th') :attr('colspan', 2) :attr('id', rowArgs.headerid) :addClass(rowArgs.class) :addClass(args.headerclass) :css('text-align', 'center') :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) elseif rowArgs.data then local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) row:attr('id', rowArgs.rowid) if rowArgs.label then row :tag('th') :attr('scope', 'row') :attr('id', rowArgs.labelid) :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') if not rowArgs.label then dataCell :attr('colspan', 2) :css('text-align', 'center') end dataCell :attr('id', rowArgs.dataid) :addClass(rowArgs.class) :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :newline() :wikitext(fixChildBoxes(rowArgs.data, 'td')) end end local function renderTitle() if not args.title then return end root :tag('caption') :addClass(args.titleclass) :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end root :tag('tr') :tag('th') :attr('colspan', 2) :addClass(args.aboveclass) :css('text-align', 'center') :css('font-size', '125%') :css('font-weight', 'bold') :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end root :tag('tr') :tag('td') :attr('colspan', '2') :addClass(args.belowclass) :css('text-align', 'center') :cssText(args.belowstyle) :newline() :wikitext(fixChildBoxes(args.below,'td')) end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addRow({ data = args['subheader' .. tostring(num)], datastyle = args.subheaderstyle or args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :cssText(args.captionstyle) :wikitext(caption) end addRow({ data = tostring(data), datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end local function renderRows() -- Gets the union of the header and data argument numbers, -- and renders them all in order using addRow. local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)], dataid = args['dataid' .. tostring(num)], labelid = args['labelid' .. tostring(num)], headerid = args['headerid' .. tostring(num)], rowid = args['rowid' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end root :tag('tr') :tag('td') :attr('colspan', '2') :css('text-align', 'right') :wikitext(navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end local function renderTrackingCategories() if args.decat ~= 'yes' then if #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') end if args.child == 'yes' and args.title then root:wikitext('[[Category:Pages which use embedded infobox templates with the title parameter]]') end end end local function _infobox() -- Specify the overall layout of the infobox, with special settings -- if the infobox is used as a 'child' inside another infobox. if args.child ~= 'yes' then root = mw.html.create('table') root :addClass((args.subbox ~= 'yes') and 'infobox' or nil) :addClass(args.bodyclass) if args.subbox == 'yes' then root :css('padding', '0') :css('border', 'none') :css('margin', '-3px') :css('width', 'auto') :css('min-width', '100%') :css('font-size', '100%') :css('clear', 'none') :css('float', 'none') :css('background-color', 'transparent') else root :css('width', '22em') end root :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end renderSubheaders() renderImages() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderTrackingCategories() return tostring(root) end local function preprocessSingleArg(argName) -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end local function preprocessArgs(prefixTable, step) -- Assign the parameters with the given prefixes to the args table, in order, in batches -- of the step size specified. This is to prevent references etc. from appearing in the -- wrong order. The prefixTable should be an array containing tables, each of which has -- two possible fields, a "prefix" string and a "depend" table. The function always parses -- parameters containing the "prefix" string, but only parses parameters in the "depend" -- table if the prefix parameter is present and non-blank. if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then moreArgumentsExist = true -- Do another loop if any arguments are found, even blank ones. preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present and not blank, or -- we are processing "prefix1" and "prefix" is present and not blank, and -- if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end function p.infobox(frame) -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end -- Parse the data parameters in the same order that the old {{infobox}} did, so that -- references etc. will display in the expected places. Parameters that depend on -- another parameter are only processed if that parameter is present, to avoid -- phantom references appearing in article reference lists. preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'}, {prefix = 'dataid'}, {prefix = 'labelid'}, {prefix = 'headerid'}, {prefix = 'rowid'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') args['italic title'] = origArgs['italic title'] -- different behaviour if blank or absent preprocessSingleArg('decat') return _infobox() end return p 035wwzvwt3y5u8arn7bo8n1hadi441i 803424 803423 2018-07-07T07:04:41Z en>Jc86035 0 the three other types of lists as well 803424 Scribunto text/plain -- -- This module implements {{Infobox}} -- local p = {} local navbar = require('Module:Navbar')._navbar local args = {} local origArgs local root local function notempty( s ) return s and s:match( '%S' ) end local function fixChildBoxes(sval, tt) if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end -- the next two lines add a newline at the end of lists for the PHP parser -- https://en.wikipedia.org/w/index.php?title=Template_talk:Infobox_musical_artist&oldid=849054481 s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') return s else return sval end end local function union(t1, t2) -- Returns the union of the values of two tables, as a sequence. local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end local function getArgNums(prefix) -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end local function addRow(rowArgs) -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. if rowArgs.header then root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :attr('id', rowArgs.rowid) :tag('th') :attr('colspan', 2) :attr('id', rowArgs.headerid) :addClass(rowArgs.class) :addClass(args.headerclass) :css('text-align', 'center') :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) elseif rowArgs.data then local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) row:attr('id', rowArgs.rowid) if rowArgs.label then row :tag('th') :attr('scope', 'row') :attr('id', rowArgs.labelid) :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') if not rowArgs.label then dataCell :attr('colspan', 2) :css('text-align', 'center') end dataCell :attr('id', rowArgs.dataid) :addClass(rowArgs.class) :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :newline() :wikitext(fixChildBoxes(rowArgs.data, 'td')) end end local function renderTitle() if not args.title then return end root :tag('caption') :addClass(args.titleclass) :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end root :tag('tr') :tag('th') :attr('colspan', 2) :addClass(args.aboveclass) :css('text-align', 'center') :css('font-size', '125%') :css('font-weight', 'bold') :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end root :tag('tr') :tag('td') :attr('colspan', '2') :addClass(args.belowclass) :css('text-align', 'center') :cssText(args.belowstyle) :newline() :wikitext(fixChildBoxes(args.below,'td')) end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addRow({ data = args['subheader' .. tostring(num)], datastyle = args.subheaderstyle or args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :cssText(args.captionstyle) :wikitext(caption) end addRow({ data = tostring(data), datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end local function renderRows() -- Gets the union of the header and data argument numbers, -- and renders them all in order using addRow. local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)], dataid = args['dataid' .. tostring(num)], labelid = args['labelid' .. tostring(num)], headerid = args['headerid' .. tostring(num)], rowid = args['rowid' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end root :tag('tr') :tag('td') :attr('colspan', '2') :css('text-align', 'right') :wikitext(navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end local function renderTrackingCategories() if args.decat ~= 'yes' then if #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') end if args.child == 'yes' and args.title then root:wikitext('[[Category:Pages which use embedded infobox templates with the title parameter]]') end end end local function _infobox() -- Specify the overall layout of the infobox, with special settings -- if the infobox is used as a 'child' inside another infobox. if args.child ~= 'yes' then root = mw.html.create('table') root :addClass((args.subbox ~= 'yes') and 'infobox' or nil) :addClass(args.bodyclass) if args.subbox == 'yes' then root :css('padding', '0') :css('border', 'none') :css('margin', '-3px') :css('width', 'auto') :css('min-width', '100%') :css('font-size', '100%') :css('clear', 'none') :css('float', 'none') :css('background-color', 'transparent') else root :css('width', '22em') end root :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end renderSubheaders() renderImages() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderTrackingCategories() return tostring(root) end local function preprocessSingleArg(argName) -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end local function preprocessArgs(prefixTable, step) -- Assign the parameters with the given prefixes to the args table, in order, in batches -- of the step size specified. This is to prevent references etc. from appearing in the -- wrong order. The prefixTable should be an array containing tables, each of which has -- two possible fields, a "prefix" string and a "depend" table. The function always parses -- parameters containing the "prefix" string, but only parses parameters in the "depend" -- table if the prefix parameter is present and non-blank. if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then moreArgumentsExist = true -- Do another loop if any arguments are found, even blank ones. preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present and not blank, or -- we are processing "prefix1" and "prefix" is present and not blank, and -- if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end function p.infobox(frame) -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end -- Parse the data parameters in the same order that the old {{infobox}} did, so that -- references etc. will display in the expected places. Parameters that depend on -- another parameter are only processed if that parameter is present, to avoid -- phantom references appearing in article reference lists. preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'}, {prefix = 'dataid'}, {prefix = 'labelid'}, {prefix = 'headerid'}, {prefix = 'rowid'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') args['italic title'] = origArgs['italic title'] -- different behaviour if blank or absent preprocessSingleArg('decat') return _infobox() end return p 3lqvq40va735qhmz9ozwwkwt6n87qbr 803425 803424 2018-08-26T15:49:54Z en>Primefac 0 avoid "no data" tracking category when "child=yes" (see [[Template_talk:Infobox#Decat_by_default_when_child=yes?]]) 803425 Scribunto text/plain -- -- This module implements {{Infobox}} -- local p = {} local navbar = require('Module:Navbar')._navbar local args = {} local origArgs local root local function notempty( s ) return s and s:match( '%S' ) end local function fixChildBoxes(sval, tt) if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end -- the next two lines add a newline at the end of lists for the PHP parser -- https://en.wikipedia.org/w/index.php?title=Template_talk:Infobox_musical_artist&oldid=849054481 -- remove when [[:phab:T191516]] is fixed or OBE s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') return s else return sval end end local function union(t1, t2) -- Returns the union of the values of two tables, as a sequence. local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end local function getArgNums(prefix) -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end local function addRow(rowArgs) -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. if rowArgs.header then root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :attr('id', rowArgs.rowid) :tag('th') :attr('colspan', 2) :attr('id', rowArgs.headerid) :addClass(rowArgs.class) :addClass(args.headerclass) :css('text-align', 'center') :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) elseif rowArgs.data then local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) row:attr('id', rowArgs.rowid) if rowArgs.label then row :tag('th') :attr('scope', 'row') :attr('id', rowArgs.labelid) :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') if not rowArgs.label then dataCell :attr('colspan', 2) :css('text-align', 'center') end dataCell :attr('id', rowArgs.dataid) :addClass(rowArgs.class) :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :newline() :wikitext(fixChildBoxes(rowArgs.data, 'td')) end end local function renderTitle() if not args.title then return end root :tag('caption') :addClass(args.titleclass) :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end root :tag('tr') :tag('th') :attr('colspan', 2) :addClass(args.aboveclass) :css('text-align', 'center') :css('font-size', '125%') :css('font-weight', 'bold') :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end root :tag('tr') :tag('td') :attr('colspan', '2') :addClass(args.belowclass) :css('text-align', 'center') :cssText(args.belowstyle) :newline() :wikitext(fixChildBoxes(args.below,'td')) end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addRow({ data = args['subheader' .. tostring(num)], datastyle = args.subheaderstyle or args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :cssText(args.captionstyle) :wikitext(caption) end addRow({ data = tostring(data), datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end local function renderRows() -- Gets the union of the header and data argument numbers, -- and renders them all in order using addRow. local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)], dataid = args['dataid' .. tostring(num)], labelid = args['labelid' .. tostring(num)], headerid = args['headerid' .. tostring(num)], rowid = args['rowid' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end root :tag('tr') :tag('td') :attr('colspan', '2') :css('text-align', 'right') :wikitext(navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end local function renderTrackingCategories() if args.decat ~= 'yes' then if args.child == 'yes' then if args.title then root:wikitext('[[Category:Pages which use embedded infobox templates with the title parameter]]') end elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') end end end local function _infobox() -- Specify the overall layout of the infobox, with special settings -- if the infobox is used as a 'child' inside another infobox. if args.child ~= 'yes' then root = mw.html.create('table') root :addClass((args.subbox ~= 'yes') and 'infobox' or nil) :addClass(args.bodyclass) if args.subbox == 'yes' then root :css('padding', '0') :css('border', 'none') :css('margin', '-3px') :css('width', 'auto') :css('min-width', '100%') :css('font-size', '100%') :css('clear', 'none') :css('float', 'none') :css('background-color', 'transparent') else root :css('width', '22em') end root :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end renderSubheaders() renderImages() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderTrackingCategories() return tostring(root) end local function preprocessSingleArg(argName) -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end local function preprocessArgs(prefixTable, step) -- Assign the parameters with the given prefixes to the args table, in order, in batches -- of the step size specified. This is to prevent references etc. from appearing in the -- wrong order. The prefixTable should be an array containing tables, each of which has -- two possible fields, a "prefix" string and a "depend" table. The function always parses -- parameters containing the "prefix" string, but only parses parameters in the "depend" -- table if the prefix parameter is present and non-blank. if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then moreArgumentsExist = true -- Do another loop if any arguments are found, even blank ones. preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present and not blank, or -- we are processing "prefix1" and "prefix" is present and not blank, and -- if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end function p.infobox(frame) -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end -- Parse the data parameters in the same order that the old {{infobox}} did, so that -- references etc. will display in the expected places. Parameters that depend on -- another parameter are only processed if that parameter is present, to avoid -- phantom references appearing in article reference lists. preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'}, {prefix = 'dataid'}, {prefix = 'labelid'}, {prefix = 'headerid'}, {prefix = 'rowid'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') args['italic title'] = origArgs['italic title'] -- different behaviour if blank or absent preprocessSingleArg('decat') return _infobox() end return p rp2tnb8ez8ttzoh83z4cf89fye7q9mo 803426 616540 2018-10-22T23:10:29Z en>Frietjes 0 per talk page 803426 Scribunto text/plain -- -- This module implements {{Infobox}} -- local p = {} local navbar = require('Module:Navbar')._navbar local args = {} local origArgs local root local function notempty( s ) return s and s:match( '%S' ) end local function fixChildBoxes(sval, tt) if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end -- the next two lines add a newline at the end of lists for the PHP parser -- https://en.wikipedia.org/w/index.php?title=Template_talk:Infobox_musical_artist&oldid=849054481 -- remove when [[:phab:T191516]] is fixed or OBE s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') return s else return sval end end local function union(t1, t2) -- Returns the union of the values of two tables, as a sequence. local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end local function getArgNums(prefix) -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end local function addRow(rowArgs) -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. if rowArgs.header then root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :attr('id', rowArgs.rowid) :tag('th') :attr('colspan', 2) :attr('id', rowArgs.headerid) :addClass(rowArgs.class) :addClass(args.headerclass) :css('text-align', 'center') :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) elseif rowArgs.data then local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) row:attr('id', rowArgs.rowid) if rowArgs.label then row :tag('th') :attr('scope', 'row') :attr('id', rowArgs.labelid) :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') if not rowArgs.label then dataCell :attr('colspan', 2) :css('text-align', 'center') end dataCell :attr('id', rowArgs.dataid) :addClass(rowArgs.class) :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :newline() :wikitext(fixChildBoxes(rowArgs.data, 'td')) end end local function renderTitle() if not args.title then return end root :tag('caption') :addClass(args.titleclass) :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end root :tag('tr') :tag('th') :attr('colspan', 2) :addClass(args.aboveclass) :css('text-align', 'center') :css('font-size', '125%') :css('font-weight', 'bold') :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end root :tag('tr') :tag('td') :attr('colspan', '2') :addClass(args.belowclass) :css('text-align', 'center') :cssText(args.belowstyle) :newline() :wikitext(fixChildBoxes(args.below,'td')) end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addRow({ data = args['subheader' .. tostring(num)], datastyle = args.subheaderstyle, rowcellstyle = args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :cssText(args.captionstyle) :wikitext(caption) end addRow({ data = tostring(data), datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end local function renderRows() -- Gets the union of the header and data argument numbers, -- and renders them all in order using addRow. local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)], dataid = args['dataid' .. tostring(num)], labelid = args['labelid' .. tostring(num)], headerid = args['headerid' .. tostring(num)], rowid = args['rowid' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end root :tag('tr') :tag('td') :attr('colspan', '2') :css('text-align', 'right') :wikitext(navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end local function renderTrackingCategories() if args.decat ~= 'yes' then if args.child == 'yes' then if args.title then root:wikitext('[[Category:Pages which use embedded infobox templates with the title parameter]]') end elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') end end end local function _infobox() -- Specify the overall layout of the infobox, with special settings -- if the infobox is used as a 'child' inside another infobox. if args.child ~= 'yes' then root = mw.html.create('table') root :addClass((args.subbox ~= 'yes') and 'infobox' or nil) :addClass(args.bodyclass) if args.subbox == 'yes' then root :css('padding', '0') :css('border', 'none') :css('margin', '-3px') :css('width', 'auto') :css('min-width', '100%') :css('font-size', '100%') :css('clear', 'none') :css('float', 'none') :css('background-color', 'transparent') else root :css('width', '22em') end root :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end renderSubheaders() renderImages() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderTrackingCategories() return tostring(root) end local function preprocessSingleArg(argName) -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end local function preprocessArgs(prefixTable, step) -- Assign the parameters with the given prefixes to the args table, in order, in batches -- of the step size specified. This is to prevent references etc. from appearing in the -- wrong order. The prefixTable should be an array containing tables, each of which has -- two possible fields, a "prefix" string and a "depend" table. The function always parses -- parameters containing the "prefix" string, but only parses parameters in the "depend" -- table if the prefix parameter is present and non-blank. if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then moreArgumentsExist = true -- Do another loop if any arguments are found, even blank ones. preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present and not blank, or -- we are processing "prefix1" and "prefix" is present and not blank, and -- if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end function p.infobox(frame) -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end -- Parse the data parameters in the same order that the old {{infobox}} did, so that -- references etc. will display in the expected places. Parameters that depend on -- another parameter are only processed if that parameter is present, to avoid -- phantom references appearing in article reference lists. preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'}, {prefix = 'dataid'}, {prefix = 'labelid'}, {prefix = 'headerid'}, {prefix = 'rowid'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') args['italic title'] = origArgs['italic title'] -- different behaviour if blank or absent preprocessSingleArg('decat') return _infobox() end return p 11ft3tooet55wlzmcb1gb28k7afkda4 803427 803426 2018-11-05T22:47:50Z en>Frietjes 0 fix for spurious <p> tag per talk page 803427 Scribunto text/plain -- -- This module implements {{Infobox}} -- local p = {} local navbar = require('Module:Navbar')._navbar local args = {} local origArgs local root local function notempty( s ) return s and s:match( '%S' ) end local function fixChildBoxes(sval, tt) if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end -- the next two lines add a newline at the end of lists for the PHP parser -- https://en.wikipedia.org/w/index.php?title=Template_talk:Infobox_musical_artist&oldid=849054481 -- remove when [[:phab:T191516]] is fixed or OBE s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1') s = mw.ustring.gsub(s, '^(%{%|)', '\n%1') return s else return sval end end local function union(t1, t2) -- Returns the union of the values of two tables, as a sequence. local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end local function getArgNums(prefix) -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end local function addRow(rowArgs) -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. if rowArgs.header then root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :attr('id', rowArgs.rowid) :tag('th') :attr('colspan', 2) :attr('id', rowArgs.headerid) :addClass(rowArgs.class) :addClass(args.headerclass) :css('text-align', 'center') :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) elseif rowArgs.data then local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) row:attr('id', rowArgs.rowid) if rowArgs.label then row :tag('th') :attr('scope', 'row') :attr('id', rowArgs.labelid) :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') if not rowArgs.label then dataCell :attr('colspan', 2) :css('text-align', 'center') end dataCell :attr('id', rowArgs.dataid) :addClass(rowArgs.class) :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.data, 'td')) end end local function renderTitle() if not args.title then return end root :tag('caption') :addClass(args.titleclass) :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end root :tag('tr') :tag('th') :attr('colspan', 2) :addClass(args.aboveclass) :css('text-align', 'center') :css('font-size', '125%') :css('font-weight', 'bold') :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end root :tag('tr') :tag('td') :attr('colspan', '2') :addClass(args.belowclass) :css('text-align', 'center') :cssText(args.belowstyle) :wikitext(fixChildBoxes(args.below,'td')) end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addRow({ data = args['subheader' .. tostring(num)], datastyle = args.subheaderstyle, rowcellstyle = args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :cssText(args.captionstyle) :wikitext(caption) end addRow({ data = tostring(data), datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end local function renderRows() -- Gets the union of the header and data argument numbers, -- and renders them all in order using addRow. local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)], dataid = args['dataid' .. tostring(num)], labelid = args['labelid' .. tostring(num)], headerid = args['headerid' .. tostring(num)], rowid = args['rowid' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end root :tag('tr') :tag('td') :attr('colspan', '2') :css('text-align', 'right') :wikitext(navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end local function renderTrackingCategories() if args.decat ~= 'yes' then if args.child == 'yes' then if args.title then root:wikitext('[[Category:Pages which use embedded infobox templates with the title parameter]]') end elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') end end end local function _infobox() -- Specify the overall layout of the infobox, with special settings -- if the infobox is used as a 'child' inside another infobox. if args.child ~= 'yes' then root = mw.html.create('table') root :addClass((args.subbox ~= 'yes') and 'infobox' or nil) :addClass(args.bodyclass) if args.subbox == 'yes' then root :css('padding', '0') :css('border', 'none') :css('margin', '-3px') :css('width', 'auto') :css('min-width', '100%') :css('font-size', '100%') :css('clear', 'none') :css('float', 'none') :css('background-color', 'transparent') else root :css('width', '22em') end root :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end renderSubheaders() renderImages() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderTrackingCategories() return tostring(root) end local function preprocessSingleArg(argName) -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end local function preprocessArgs(prefixTable, step) -- Assign the parameters with the given prefixes to the args table, in order, in batches -- of the step size specified. This is to prevent references etc. from appearing in the -- wrong order. The prefixTable should be an array containing tables, each of which has -- two possible fields, a "prefix" string and a "depend" table. The function always parses -- parameters containing the "prefix" string, but only parses parameters in the "depend" -- table if the prefix parameter is present and non-blank. if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then moreArgumentsExist = true -- Do another loop if any arguments are found, even blank ones. preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present and not blank, or -- we are processing "prefix1" and "prefix" is present and not blank, and -- if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end function p.infobox(frame) -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end -- Parse the data parameters in the same order that the old {{infobox}} did, so that -- references etc. will display in the expected places. Parameters that depend on -- another parameter are only processed if that parameter is present, to avoid -- phantom references appearing in article reference lists. preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'}, {prefix = 'dataid'}, {prefix = 'labelid'}, {prefix = 'headerid'}, {prefix = 'rowid'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') args['italic title'] = origArgs['italic title'] -- different behaviour if blank or absent preprocessSingleArg('decat') return _infobox() end return p fqwlmnh999j8i9ezhfasls5wwtsbxv1 803428 803427 2019-07-13T12:47:29Z en>Gonnym 0 Added more options to Italic Title per discussion at [[Template talk:Infobox#Italics part deux]] 803428 Scribunto text/plain -- -- This module implements {{Infobox}} -- local p = {} local navbar = require('Module:Navbar')._navbar local args = {} local origArgs local root local function notempty( s ) return s and s:match( '%S' ) end local function fixChildBoxes(sval, tt) if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end -- the next two lines add a newline at the end of lists for the PHP parser -- https://en.wikipedia.org/w/index.php?title=Template_talk:Infobox_musical_artist&oldid=849054481 -- remove when [[:phab:T191516]] is fixed or OBE s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1') s = mw.ustring.gsub(s, '^(%{%|)', '\n%1') return s else return sval end end local function union(t1, t2) -- Returns the union of the values of two tables, as a sequence. local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end local function getArgNums(prefix) -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end local function addRow(rowArgs) -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. if rowArgs.header then root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :attr('id', rowArgs.rowid) :tag('th') :attr('colspan', 2) :attr('id', rowArgs.headerid) :addClass(rowArgs.class) :addClass(args.headerclass) :css('text-align', 'center') :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) elseif rowArgs.data then local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) row:attr('id', rowArgs.rowid) if rowArgs.label then row :tag('th') :attr('scope', 'row') :attr('id', rowArgs.labelid) :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') if not rowArgs.label then dataCell :attr('colspan', 2) :css('text-align', 'center') end dataCell :attr('id', rowArgs.dataid) :addClass(rowArgs.class) :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.data, 'td')) end end local function renderTitle() if not args.title then return end root :tag('caption') :addClass(args.titleclass) :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end root :tag('tr') :tag('th') :attr('colspan', 2) :addClass(args.aboveclass) :css('text-align', 'center') :css('font-size', '125%') :css('font-weight', 'bold') :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end root :tag('tr') :tag('td') :attr('colspan', '2') :addClass(args.belowclass) :css('text-align', 'center') :cssText(args.belowstyle) :wikitext(fixChildBoxes(args.below,'td')) end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addRow({ data = args['subheader' .. tostring(num)], datastyle = args.subheaderstyle, rowcellstyle = args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :cssText(args.captionstyle) :wikitext(caption) end addRow({ data = tostring(data), datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end local function renderRows() -- Gets the union of the header and data argument numbers, -- and renders them all in order using addRow. local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)], dataid = args['dataid' .. tostring(num)], labelid = args['labelid' .. tostring(num)], headerid = args['headerid' .. tostring(num)], rowid = args['rowid' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end root :tag('tr') :tag('td') :attr('colspan', '2') :css('text-align', 'right') :wikitext(navbar{ args.name, mini = 1, }) end --[[ Function handles the page's title italicization. -- If the value of "italic title" is set to "force" or "yes", titles with no parentheses () are fully italicised, and titles which contain parentheses are italicised before the first opening parenthesis. -- If the value of "italic title" is set to "all", the complete title, including the text in parentheses is italicized. -- If the value of "italic_string" is entered, instead of italicizing the whole title, it will italicize only some specific text. -- Note that if the specific text is in the disambiguation then both |italic title=all and |italic_string=<text> will need to be used. -- -- Optionale combinations: --- No italics. --- Italicized page name without disambiguation. --- Italicized complete page name, including parentheses. --- Italicized part of the text, not including disambiguation. --- Italicized part of the text, including parentheses. --]] local function renderItalicTitle() local italicTitleModule = require("Module:Italic title")._main local italicTitleOption = args['italic title'] local italicTitleString = args['italic_string'] local italicTitleArg = {} if (italicTitleOption) then local italicLowercase = mw.ustring.lower(italicTitleOption) if (italicLowercase == "no") then -- If set to "no" or any other disabling parameter, do nothing. return elseif (italicLowercase == '' or italicLowercase == 'force' or italicLowercase == 'yes') then root:wikitext(italicTitleModule({})) return elseif (italicLowercase == 'all') then italicTitleArg.all = "yes" else -- Do nothing. return end end if (italicTitleString) then italicTitleArg.string = italicTitleString end root:wikitext(italicTitleModule(italicTitleArg)) end local function renderTrackingCategories() if args.decat ~= 'yes' then if args.child == 'yes' then if args.title then root:wikitext('[[Category:Pages which use embedded infobox templates with the title parameter]]') end elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') end end end local function _infobox() -- Specify the overall layout of the infobox, with special settings -- if the infobox is used as a 'child' inside another infobox. if args.child ~= 'yes' then root = mw.html.create('table') root :addClass((args.subbox ~= 'yes') and 'infobox' or nil) :addClass(args.bodyclass) if args.subbox == 'yes' then root :css('padding', '0') :css('border', 'none') :css('margin', '-3px') :css('width', 'auto') :css('min-width', '100%') :css('font-size', '100%') :css('clear', 'none') :css('float', 'none') :css('background-color', 'transparent') else root :css('width', '22em') end root :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end renderSubheaders() renderImages() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderTrackingCategories() return tostring(root) end local function preprocessSingleArg(argName) -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end local function preprocessArgs(prefixTable, step) -- Assign the parameters with the given prefixes to the args table, in order, in batches -- of the step size specified. This is to prevent references etc. from appearing in the -- wrong order. The prefixTable should be an array containing tables, each of which has -- two possible fields, a "prefix" string and a "depend" table. The function always parses -- parameters containing the "prefix" string, but only parses parameters in the "depend" -- table if the prefix parameter is present and non-blank. if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then moreArgumentsExist = true -- Do another loop if any arguments are found, even blank ones. preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present and not blank, or -- we are processing "prefix1" and "prefix" is present and not blank, and -- if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end function p.infobox(frame) -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end -- Parse the data parameters in the same order that the old {{infobox}} did, so that -- references etc. will display in the expected places. Parameters that depend on -- another parameter are only processed if that parameter is present, to avoid -- phantom references appearing in article reference lists. preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'}, {prefix = 'dataid'}, {prefix = 'labelid'}, {prefix = 'headerid'}, {prefix = 'rowid'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') args['italic title'] = origArgs['italic title'] -- different behaviour if blank or absent preprocessSingleArg('italic_string') preprocessSingleArg('decat') return _infobox() end return p agn773u1y9zv50ljbgviq70vosaygkr 803429 803428 2019-07-13T13:05:36Z en>Gonnym 0 Undid revision 906073835 by [[Special:Contributions/Gonnym|Gonnym]] ([[User talk:Gonnym|talk]]) errors in code were reported 803429 Scribunto text/plain -- -- This module implements {{Infobox}} -- local p = {} local navbar = require('Module:Navbar')._navbar local args = {} local origArgs local root local function notempty( s ) return s and s:match( '%S' ) end local function fixChildBoxes(sval, tt) if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end -- the next two lines add a newline at the end of lists for the PHP parser -- https://en.wikipedia.org/w/index.php?title=Template_talk:Infobox_musical_artist&oldid=849054481 -- remove when [[:phab:T191516]] is fixed or OBE s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1') s = mw.ustring.gsub(s, '^(%{%|)', '\n%1') return s else return sval end end local function union(t1, t2) -- Returns the union of the values of two tables, as a sequence. local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end local function getArgNums(prefix) -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end local function addRow(rowArgs) -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. if rowArgs.header then root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :attr('id', rowArgs.rowid) :tag('th') :attr('colspan', 2) :attr('id', rowArgs.headerid) :addClass(rowArgs.class) :addClass(args.headerclass) :css('text-align', 'center') :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) elseif rowArgs.data then local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) row:attr('id', rowArgs.rowid) if rowArgs.label then row :tag('th') :attr('scope', 'row') :attr('id', rowArgs.labelid) :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') if not rowArgs.label then dataCell :attr('colspan', 2) :css('text-align', 'center') end dataCell :attr('id', rowArgs.dataid) :addClass(rowArgs.class) :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.data, 'td')) end end local function renderTitle() if not args.title then return end root :tag('caption') :addClass(args.titleclass) :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end root :tag('tr') :tag('th') :attr('colspan', 2) :addClass(args.aboveclass) :css('text-align', 'center') :css('font-size', '125%') :css('font-weight', 'bold') :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end root :tag('tr') :tag('td') :attr('colspan', '2') :addClass(args.belowclass) :css('text-align', 'center') :cssText(args.belowstyle) :wikitext(fixChildBoxes(args.below,'td')) end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addRow({ data = args['subheader' .. tostring(num)], datastyle = args.subheaderstyle, rowcellstyle = args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :cssText(args.captionstyle) :wikitext(caption) end addRow({ data = tostring(data), datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end local function renderRows() -- Gets the union of the header and data argument numbers, -- and renders them all in order using addRow. local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)], dataid = args['dataid' .. tostring(num)], labelid = args['labelid' .. tostring(num)], headerid = args['headerid' .. tostring(num)], rowid = args['rowid' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end root :tag('tr') :tag('td') :attr('colspan', '2') :css('text-align', 'right') :wikitext(navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end local function renderTrackingCategories() if args.decat ~= 'yes' then if args.child == 'yes' then if args.title then root:wikitext('[[Category:Pages which use embedded infobox templates with the title parameter]]') end elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') end end end local function _infobox() -- Specify the overall layout of the infobox, with special settings -- if the infobox is used as a 'child' inside another infobox. if args.child ~= 'yes' then root = mw.html.create('table') root :addClass((args.subbox ~= 'yes') and 'infobox' or nil) :addClass(args.bodyclass) if args.subbox == 'yes' then root :css('padding', '0') :css('border', 'none') :css('margin', '-3px') :css('width', 'auto') :css('min-width', '100%') :css('font-size', '100%') :css('clear', 'none') :css('float', 'none') :css('background-color', 'transparent') else root :css('width', '22em') end root :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end renderSubheaders() renderImages() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderTrackingCategories() return tostring(root) end local function preprocessSingleArg(argName) -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end local function preprocessArgs(prefixTable, step) -- Assign the parameters with the given prefixes to the args table, in order, in batches -- of the step size specified. This is to prevent references etc. from appearing in the -- wrong order. The prefixTable should be an array containing tables, each of which has -- two possible fields, a "prefix" string and a "depend" table. The function always parses -- parameters containing the "prefix" string, but only parses parameters in the "depend" -- table if the prefix parameter is present and non-blank. if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then moreArgumentsExist = true -- Do another loop if any arguments are found, even blank ones. preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present and not blank, or -- we are processing "prefix1" and "prefix" is present and not blank, and -- if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end function p.infobox(frame) -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end -- Parse the data parameters in the same order that the old {{infobox}} did, so that -- references etc. will display in the expected places. Parameters that depend on -- another parameter are only processed if that parameter is present, to avoid -- phantom references appearing in article reference lists. preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'}, {prefix = 'dataid'}, {prefix = 'labelid'}, {prefix = 'headerid'}, {prefix = 'rowid'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') args['italic title'] = origArgs['italic title'] -- different behaviour if blank or absent preprocessSingleArg('decat') return _infobox() end return p fqwlmnh999j8i9ezhfasls5wwtsbxv1 803430 803429 2020-05-13T14:37:24Z en>Frietjes 0 tracking with [[Category:Pages which use infobox templates with ignored data cells]] for discussion on talk page 803430 Scribunto text/plain -- -- This module implements {{Infobox}} -- local p = {} local navbar = require('Module:Navbar')._navbar local args = {} local origArgs local root local function notempty( s ) return s and s:match( '%S' ) end local function fixChildBoxes(sval, tt) if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end -- the next two lines add a newline at the end of lists for the PHP parser -- https://en.wikipedia.org/w/index.php?title=Template_talk:Infobox_musical_artist&oldid=849054481 -- remove when [[:phab:T191516]] is fixed or OBE s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1') s = mw.ustring.gsub(s, '^(%{%|)', '\n%1') return s else return sval end end local function union(t1, t2) -- Returns the union of the values of two tables, as a sequence. local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end local function getArgNums(prefix) -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end local function addRow(rowArgs) -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. if rowArgs.header then root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :attr('id', rowArgs.rowid) :tag('th') :attr('colspan', 2) :attr('id', rowArgs.headerid) :addClass(rowArgs.class) :addClass(args.headerclass) :css('text-align', 'center') :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) if rowArgs.data then root:wikitext('[[Category:Pages which use infobox templates with ignored data cells]]') end elseif rowArgs.data then local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) row:attr('id', rowArgs.rowid) if rowArgs.label then row :tag('th') :attr('scope', 'row') :attr('id', rowArgs.labelid) :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') if not rowArgs.label then dataCell :attr('colspan', 2) :css('text-align', 'center') end dataCell :attr('id', rowArgs.dataid) :addClass(rowArgs.class) :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.data, 'td')) end end local function renderTitle() if not args.title then return end root :tag('caption') :addClass(args.titleclass) :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end root :tag('tr') :tag('th') :attr('colspan', 2) :addClass(args.aboveclass) :css('text-align', 'center') :css('font-size', '125%') :css('font-weight', 'bold') :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end root :tag('tr') :tag('td') :attr('colspan', '2') :addClass(args.belowclass) :css('text-align', 'center') :cssText(args.belowstyle) :wikitext(fixChildBoxes(args.below,'td')) end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addRow({ data = args['subheader' .. tostring(num)], datastyle = args.subheaderstyle, rowcellstyle = args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :cssText(args.captionstyle) :wikitext(caption) end addRow({ data = tostring(data), datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end local function renderRows() -- Gets the union of the header and data argument numbers, -- and renders them all in order using addRow. local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)], dataid = args['dataid' .. tostring(num)], labelid = args['labelid' .. tostring(num)], headerid = args['headerid' .. tostring(num)], rowid = args['rowid' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end root :tag('tr') :tag('td') :attr('colspan', '2') :css('text-align', 'right') :wikitext(navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end local function renderTrackingCategories() if args.decat ~= 'yes' then if args.child == 'yes' then if args.title then root:wikitext('[[Category:Pages which use embedded infobox templates with the title parameter]]') end elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') end end end local function _infobox() -- Specify the overall layout of the infobox, with special settings -- if the infobox is used as a 'child' inside another infobox. if args.child ~= 'yes' then root = mw.html.create('table') root :addClass((args.subbox ~= 'yes') and 'infobox' or nil) :addClass(args.bodyclass) if args.subbox == 'yes' then root :css('padding', '0') :css('border', 'none') :css('margin', '-3px') :css('width', 'auto') :css('min-width', '100%') :css('font-size', '100%') :css('clear', 'none') :css('float', 'none') :css('background-color', 'transparent') else root :css('width', '22em') end root :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end renderSubheaders() renderImages() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderTrackingCategories() return tostring(root) end local function preprocessSingleArg(argName) -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end local function preprocessArgs(prefixTable, step) -- Assign the parameters with the given prefixes to the args table, in order, in batches -- of the step size specified. This is to prevent references etc. from appearing in the -- wrong order. The prefixTable should be an array containing tables, each of which has -- two possible fields, a "prefix" string and a "depend" table. The function always parses -- parameters containing the "prefix" string, but only parses parameters in the "depend" -- table if the prefix parameter is present and non-blank. if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then moreArgumentsExist = true -- Do another loop if any arguments are found, even blank ones. preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present and not blank, or -- we are processing "prefix1" and "prefix" is present and not blank, and -- if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end function p.infobox(frame) -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end -- Parse the data parameters in the same order that the old {{infobox}} did, so that -- references etc. will display in the expected places. Parameters that depend on -- another parameter are only processed if that parameter is present, to avoid -- phantom references appearing in article reference lists. preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'}, {prefix = 'dataid'}, {prefix = 'labelid'}, {prefix = 'headerid'}, {prefix = 'rowid'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') args['italic title'] = origArgs['italic title'] -- different behaviour if blank or absent preprocessSingleArg('decat') return _infobox() end return p hmzwh371r9nydsvmocsdktbi7444zqo 803431 803430 2020-05-13T19:13:58Z en>Frietjes 0 fix for categories triggering blank data cells per talk page (and spaces to tabs per edits by RexxS) 803431 Scribunto text/plain -- -- This module implements {{Infobox}} -- local p = {} local navbar = require('Module:Navbar')._navbar local args = {} local origArgs local root local function notempty( s ) return s and s:match( '%S' ) end local function fixChildBoxes(sval, tt) if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end -- the next two lines add a newline at the end of lists for the PHP parser -- https://en.wikipedia.org/w/index.php?title=Template_talk:Infobox_musical_artist&oldid=849054481 -- remove when [[:phab:T191516]] is fixed or OBE s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1') s = mw.ustring.gsub(s, '^(%{%|)', '\n%1') return s else return sval end end local function union(t1, t2) -- Returns the union of the values of two tables, as a sequence. local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end local function getArgNums(prefix) -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end local function addRow(rowArgs) -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. if rowArgs.header then root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :attr('id', rowArgs.rowid) :tag('th') :attr('colspan', 2) :attr('id', rowArgs.headerid) :addClass(rowArgs.class) :addClass(args.headerclass) :css('text-align', 'center') :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) if rowArgs.data then root:wikitext('[[Category:Pages which use infobox templates with ignored data cells]]') end elseif rowArgs.data then local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) row:attr('id', rowArgs.rowid) if rowArgs.label then row :tag('th') :attr('scope', 'row') :attr('id', rowArgs.labelid) :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') if not rowArgs.label then dataCell :attr('colspan', 2) :css('text-align', 'center') end dataCell :attr('id', rowArgs.dataid) :addClass(rowArgs.class) :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.data, 'td')) end end local function renderTitle() if not args.title then return end root :tag('caption') :addClass(args.titleclass) :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end root :tag('tr') :tag('th') :attr('colspan', 2) :addClass(args.aboveclass) :css('text-align', 'center') :css('font-size', '125%') :css('font-weight', 'bold') :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end root :tag('tr') :tag('td') :attr('colspan', '2') :addClass(args.belowclass) :css('text-align', 'center') :cssText(args.belowstyle) :wikitext(fixChildBoxes(args.below,'td')) end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addRow({ data = args['subheader' .. tostring(num)], datastyle = args.subheaderstyle, rowcellstyle = args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :cssText(args.captionstyle) :wikitext(caption) end addRow({ data = tostring(data), datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end local function renderRows() -- Gets the union of the header and data argument numbers, -- and renders them all in order using addRow. local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) local row_style for k, num in ipairs(rownums) do local dval = args['data' .. tostring(num)] if dval and dval:gsub('%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]', ''):match('%S') then row_style = args['rowstyle' .. tostring(num)] else row_style = 'display:none' end addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], rowstyle = row_style, rowcellstyle = args['rowcellstyle' .. tostring(num)], dataid = args['dataid' .. tostring(num)], labelid = args['labelid' .. tostring(num)], headerid = args['headerid' .. tostring(num)], rowid = args['rowid' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end root :tag('tr') :tag('td') :attr('colspan', '2') :css('text-align', 'right') :wikitext(navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end local function renderTrackingCategories() if args.decat ~= 'yes' then if args.child == 'yes' then if args.title then root:wikitext('[[Category:Pages which use embedded infobox templates with the title parameter]]') end elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') end end end local function _infobox() -- Specify the overall layout of the infobox, with special settings -- if the infobox is used as a 'child' inside another infobox. if args.child ~= 'yes' then root = mw.html.create('table') root :addClass((args.subbox ~= 'yes') and 'infobox' or nil) :addClass(args.bodyclass) if args.subbox == 'yes' then root :css('padding', '0') :css('border', 'none') :css('margin', '-3px') :css('width', 'auto') :css('min-width', '100%') :css('font-size', '100%') :css('clear', 'none') :css('float', 'none') :css('background-color', 'transparent') else root :css('width', '22em') end root :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end renderSubheaders() renderImages() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderTrackingCategories() return tostring(root) end local function preprocessSingleArg(argName) -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end local function preprocessArgs(prefixTable, step) -- Assign the parameters with the given prefixes to the args table, in order, in batches -- of the step size specified. This is to prevent references etc. from appearing in the -- wrong order. The prefixTable should be an array containing tables, each of which has -- two possible fields, a "prefix" string and a "depend" table. The function always parses -- parameters containing the "prefix" string, but only parses parameters in the "depend" -- table if the prefix parameter is present and non-blank. if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then moreArgumentsExist = true -- Do another loop if any arguments are found, even blank ones. preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present and not blank, or -- we are processing "prefix1" and "prefix" is present and not blank, and -- if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end function p.infobox(frame) -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end -- Parse the data parameters in the same order that the old {{infobox}} did, so that -- references etc. will display in the expected places. Parameters that depend on -- another parameter are only processed if that parameter is present, to avoid -- phantom references appearing in article reference lists. preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'}, {prefix = 'dataid'}, {prefix = 'labelid'}, {prefix = 'headerid'}, {prefix = 'rowid'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') args['italic title'] = origArgs['italic title'] -- different behaviour if blank or absent preprocessSingleArg('decat') return _infobox() end return p 9hebohbiq10sn09rn6kzoelpztnif2q 803432 803431 2020-05-13T19:49:01Z en>Galobtter 0 Undid revision 956509357 by [[Special:Contributions/Frietjes|Frietjes]] ([[User talk:Frietjes|talk]]) breaks headers in infobox 803432 Scribunto text/plain -- -- This module implements {{Infobox}} -- local p = {} local navbar = require('Module:Navbar')._navbar local args = {} local origArgs local root local function notempty( s ) return s and s:match( '%S' ) end local function fixChildBoxes(sval, tt) if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end -- the next two lines add a newline at the end of lists for the PHP parser -- https://en.wikipedia.org/w/index.php?title=Template_talk:Infobox_musical_artist&oldid=849054481 -- remove when [[:phab:T191516]] is fixed or OBE s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1') s = mw.ustring.gsub(s, '^(%{%|)', '\n%1') return s else return sval end end local function union(t1, t2) -- Returns the union of the values of two tables, as a sequence. local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end local function getArgNums(prefix) -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end local function addRow(rowArgs) -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. if rowArgs.header then root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :attr('id', rowArgs.rowid) :tag('th') :attr('colspan', 2) :attr('id', rowArgs.headerid) :addClass(rowArgs.class) :addClass(args.headerclass) :css('text-align', 'center') :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) if rowArgs.data then root:wikitext('[[Category:Pages which use infobox templates with ignored data cells]]') end elseif rowArgs.data then local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) row:attr('id', rowArgs.rowid) if rowArgs.label then row :tag('th') :attr('scope', 'row') :attr('id', rowArgs.labelid) :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') if not rowArgs.label then dataCell :attr('colspan', 2) :css('text-align', 'center') end dataCell :attr('id', rowArgs.dataid) :addClass(rowArgs.class) :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.data, 'td')) end end local function renderTitle() if not args.title then return end root :tag('caption') :addClass(args.titleclass) :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end root :tag('tr') :tag('th') :attr('colspan', 2) :addClass(args.aboveclass) :css('text-align', 'center') :css('font-size', '125%') :css('font-weight', 'bold') :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end root :tag('tr') :tag('td') :attr('colspan', '2') :addClass(args.belowclass) :css('text-align', 'center') :cssText(args.belowstyle) :wikitext(fixChildBoxes(args.below,'td')) end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addRow({ data = args['subheader' .. tostring(num)], datastyle = args.subheaderstyle, rowcellstyle = args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :cssText(args.captionstyle) :wikitext(caption) end addRow({ data = tostring(data), datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end local function renderRows() -- Gets the union of the header and data argument numbers, -- and renders them all in order using addRow. local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)], dataid = args['dataid' .. tostring(num)], labelid = args['labelid' .. tostring(num)], headerid = args['headerid' .. tostring(num)], rowid = args['rowid' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end root :tag('tr') :tag('td') :attr('colspan', '2') :css('text-align', 'right') :wikitext(navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end local function renderTrackingCategories() if args.decat ~= 'yes' then if args.child == 'yes' then if args.title then root:wikitext('[[Category:Pages which use embedded infobox templates with the title parameter]]') end elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') end end end local function _infobox() -- Specify the overall layout of the infobox, with special settings -- if the infobox is used as a 'child' inside another infobox. if args.child ~= 'yes' then root = mw.html.create('table') root :addClass((args.subbox ~= 'yes') and 'infobox' or nil) :addClass(args.bodyclass) if args.subbox == 'yes' then root :css('padding', '0') :css('border', 'none') :css('margin', '-3px') :css('width', 'auto') :css('min-width', '100%') :css('font-size', '100%') :css('clear', 'none') :css('float', 'none') :css('background-color', 'transparent') else root :css('width', '22em') end root :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end renderSubheaders() renderImages() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderTrackingCategories() return tostring(root) end local function preprocessSingleArg(argName) -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end local function preprocessArgs(prefixTable, step) -- Assign the parameters with the given prefixes to the args table, in order, in batches -- of the step size specified. This is to prevent references etc. from appearing in the -- wrong order. The prefixTable should be an array containing tables, each of which has -- two possible fields, a "prefix" string and a "depend" table. The function always parses -- parameters containing the "prefix" string, but only parses parameters in the "depend" -- table if the prefix parameter is present and non-blank. if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then moreArgumentsExist = true -- Do another loop if any arguments are found, even blank ones. preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present and not blank, or -- we are processing "prefix1" and "prefix" is present and not blank, and -- if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end function p.infobox(frame) -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end -- Parse the data parameters in the same order that the old {{infobox}} did, so that -- references etc. will display in the expected places. Parameters that depend on -- another parameter are only processed if that parameter is present, to avoid -- phantom references appearing in article reference lists. preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'}, {prefix = 'dataid'}, {prefix = 'labelid'}, {prefix = 'headerid'}, {prefix = 'rowid'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') args['italic title'] = origArgs['italic title'] -- different behaviour if blank or absent preprocessSingleArg('decat') return _infobox() end return p hmzwh371r9nydsvmocsdktbi7444zqo 803433 803432 2020-05-13T19:50:38Z en>Frietjes 0 fixed 803433 Scribunto text/plain -- -- This module implements {{Infobox}} -- local p = {} local navbar = require('Module:Navbar')._navbar local args = {} local origArgs local root local function notempty( s ) return s and s:match( '%S' ) end local function fixChildBoxes(sval, tt) if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end -- the next two lines add a newline at the end of lists for the PHP parser -- https://en.wikipedia.org/w/index.php?title=Template_talk:Infobox_musical_artist&oldid=849054481 -- remove when [[:phab:T191516]] is fixed or OBE s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1') s = mw.ustring.gsub(s, '^(%{%|)', '\n%1') return s else return sval end end local function union(t1, t2) -- Returns the union of the values of two tables, as a sequence. local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end local function getArgNums(prefix) -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end local function addRow(rowArgs) -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. if rowArgs.header then root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :attr('id', rowArgs.rowid) :tag('th') :attr('colspan', 2) :attr('id', rowArgs.headerid) :addClass(rowArgs.class) :addClass(args.headerclass) :css('text-align', 'center') :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) if rowArgs.data then root:wikitext('[[Category:Pages which use infobox templates with ignored data cells]]') end elseif rowArgs.data then local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) row:attr('id', rowArgs.rowid) if rowArgs.label then row :tag('th') :attr('scope', 'row') :attr('id', rowArgs.labelid) :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') if not rowArgs.label then dataCell :attr('colspan', 2) :css('text-align', 'center') end dataCell :attr('id', rowArgs.dataid) :addClass(rowArgs.class) :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.data, 'td')) end end local function renderTitle() if not args.title then return end root :tag('caption') :addClass(args.titleclass) :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end root :tag('tr') :tag('th') :attr('colspan', 2) :addClass(args.aboveclass) :css('text-align', 'center') :css('font-size', '125%') :css('font-weight', 'bold') :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end root :tag('tr') :tag('td') :attr('colspan', '2') :addClass(args.belowclass) :css('text-align', 'center') :cssText(args.belowstyle) :wikitext(fixChildBoxes(args.below,'td')) end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addRow({ data = args['subheader' .. tostring(num)], datastyle = args.subheaderstyle, rowcellstyle = args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :cssText(args.captionstyle) :wikitext(caption) end addRow({ data = tostring(data), datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end local function renderRows() -- Gets the union of the header and data argument numbers, -- and renders them all in order using addRow. local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) local row_style for k, num in ipairs(rownums) do local dval = args['data' .. tostring(num)] row_style = args['rowstyle' .. tostring(num)] if args['header' .. tostring(num)] then row_style = args['rowstyle' .. tostring(num)] elseif dval and dval:gsub('%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]', ''):match('%S') then row_style = args['rowstyle' .. tostring(num)] else row_style = 'display:none' end addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], rowstyle = row_style, rowcellstyle = args['rowcellstyle' .. tostring(num)], dataid = args['dataid' .. tostring(num)], labelid = args['labelid' .. tostring(num)], headerid = args['headerid' .. tostring(num)], rowid = args['rowid' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end root :tag('tr') :tag('td') :attr('colspan', '2') :css('text-align', 'right') :wikitext(navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end local function renderTrackingCategories() if args.decat ~= 'yes' then if args.child == 'yes' then if args.title then root:wikitext('[[Category:Pages which use embedded infobox templates with the title parameter]]') end elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') end end end local function _infobox() -- Specify the overall layout of the infobox, with special settings -- if the infobox is used as a 'child' inside another infobox. if args.child ~= 'yes' then root = mw.html.create('table') root :addClass((args.subbox ~= 'yes') and 'infobox' or nil) :addClass(args.bodyclass) if args.subbox == 'yes' then root :css('padding', '0') :css('border', 'none') :css('margin', '-3px') :css('width', 'auto') :css('min-width', '100%') :css('font-size', '100%') :css('clear', 'none') :css('float', 'none') :css('background-color', 'transparent') else root :css('width', '22em') end root :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end renderSubheaders() renderImages() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderTrackingCategories() return tostring(root) end local function preprocessSingleArg(argName) -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end local function preprocessArgs(prefixTable, step) -- Assign the parameters with the given prefixes to the args table, in order, in batches -- of the step size specified. This is to prevent references etc. from appearing in the -- wrong order. The prefixTable should be an array containing tables, each of which has -- two possible fields, a "prefix" string and a "depend" table. The function always parses -- parameters containing the "prefix" string, but only parses parameters in the "depend" -- table if the prefix parameter is present and non-blank. if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then moreArgumentsExist = true -- Do another loop if any arguments are found, even blank ones. preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present and not blank, or -- we are processing "prefix1" and "prefix" is present and not blank, and -- if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end function p.infobox(frame) -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end -- Parse the data parameters in the same order that the old {{infobox}} did, so that -- references etc. will display in the expected places. Parameters that depend on -- another parameter are only processed if that parameter is present, to avoid -- phantom references appearing in article reference lists. preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'}, {prefix = 'dataid'}, {prefix = 'labelid'}, {prefix = 'headerid'}, {prefix = 'rowid'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') args['italic title'] = origArgs['italic title'] -- different behaviour if blank or absent preprocessSingleArg('decat') return _infobox() end return p 6dsiqtdlz17hw0qgt0jrpxeupvlglq2 803434 803433 2020-05-13T20:01:15Z en>Frietjes 0 simplify (tested in sandbox) 803434 Scribunto text/plain -- -- This module implements {{Infobox}} -- local p = {} local navbar = require('Module:Navbar')._navbar local args = {} local origArgs local root local function notempty( s ) return s and s:match( '%S' ) end local function fixChildBoxes(sval, tt) if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end -- the next two lines add a newline at the end of lists for the PHP parser -- https://en.wikipedia.org/w/index.php?title=Template_talk:Infobox_musical_artist&oldid=849054481 -- remove when [[:phab:T191516]] is fixed or OBE s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1') s = mw.ustring.gsub(s, '^(%{%|)', '\n%1') return s else return sval end end local function union(t1, t2) -- Returns the union of the values of two tables, as a sequence. local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end local function getArgNums(prefix) -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end local function addRow(rowArgs) -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. if rowArgs.header then root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :attr('id', rowArgs.rowid) :tag('th') :attr('colspan', 2) :attr('id', rowArgs.headerid) :addClass(rowArgs.class) :addClass(args.headerclass) :css('text-align', 'center') :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) if rowArgs.data then root:wikitext('[[Category:Pages which use infobox templates with ignored data cells]]') end elseif rowArgs.data then if not rowArgs.data:gsub('%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]', ''):match('^%S') then rowArgs.rowstyle = 'display:none' end local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) row:attr('id', rowArgs.rowid) if rowArgs.label then row :tag('th') :attr('scope', 'row') :attr('id', rowArgs.labelid) :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') if not rowArgs.label then dataCell :attr('colspan', 2) :css('text-align', 'center') end dataCell :attr('id', rowArgs.dataid) :addClass(rowArgs.class) :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.data, 'td')) end end local function renderTitle() if not args.title then return end root :tag('caption') :addClass(args.titleclass) :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end root :tag('tr') :tag('th') :attr('colspan', 2) :addClass(args.aboveclass) :css('text-align', 'center') :css('font-size', '125%') :css('font-weight', 'bold') :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end root :tag('tr') :tag('td') :attr('colspan', '2') :addClass(args.belowclass) :css('text-align', 'center') :cssText(args.belowstyle) :wikitext(fixChildBoxes(args.below,'td')) end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addRow({ data = args['subheader' .. tostring(num)], datastyle = args.subheaderstyle, rowcellstyle = args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :cssText(args.captionstyle) :wikitext(caption) end addRow({ data = tostring(data), datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end local function renderRows() -- Gets the union of the header and data argument numbers, -- and renders them all in order using addRow. local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)], dataid = args['dataid' .. tostring(num)], labelid = args['labelid' .. tostring(num)], headerid = args['headerid' .. tostring(num)], rowid = args['rowid' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end root :tag('tr') :tag('td') :attr('colspan', '2') :css('text-align', 'right') :wikitext(navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end local function renderTrackingCategories() if args.decat ~= 'yes' then if args.child == 'yes' then if args.title then root:wikitext('[[Category:Pages which use embedded infobox templates with the title parameter]]') end elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') end end end local function _infobox() -- Specify the overall layout of the infobox, with special settings -- if the infobox is used as a 'child' inside another infobox. if args.child ~= 'yes' then root = mw.html.create('table') root :addClass((args.subbox ~= 'yes') and 'infobox' or nil) :addClass(args.bodyclass) if args.subbox == 'yes' then root :css('padding', '0') :css('border', 'none') :css('margin', '-3px') :css('width', 'auto') :css('min-width', '100%') :css('font-size', '100%') :css('clear', 'none') :css('float', 'none') :css('background-color', 'transparent') else root :css('width', '22em') end root :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end renderSubheaders() renderImages() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderTrackingCategories() return tostring(root) end local function preprocessSingleArg(argName) -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end local function preprocessArgs(prefixTable, step) -- Assign the parameters with the given prefixes to the args table, in order, in batches -- of the step size specified. This is to prevent references etc. from appearing in the -- wrong order. The prefixTable should be an array containing tables, each of which has -- two possible fields, a "prefix" string and a "depend" table. The function always parses -- parameters containing the "prefix" string, but only parses parameters in the "depend" -- table if the prefix parameter is present and non-blank. if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then moreArgumentsExist = true -- Do another loop if any arguments are found, even blank ones. preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present and not blank, or -- we are processing "prefix1" and "prefix" is present and not blank, and -- if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end function p.infobox(frame) -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end -- Parse the data parameters in the same order that the old {{infobox}} did, so that -- references etc. will display in the expected places. Parameters that depend on -- another parameter are only processed if that parameter is present, to avoid -- phantom references appearing in article reference lists. preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'}, {prefix = 'dataid'}, {prefix = 'labelid'}, {prefix = 'headerid'}, {prefix = 'rowid'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') args['italic title'] = origArgs['italic title'] -- different behaviour if blank or absent preprocessSingleArg('decat') return _infobox() end return p 7j4mp6z10ropygjyub7mtxdrh0n1rn3 803435 803434 2020-05-16T21:14:51Z en>Ahecht 0 Allow arguments passed via #invoke to override those passed into the invoking template. 803435 Scribunto text/plain -- -- This module implements {{Infobox}} -- local p = {} local navbar = require('Module:Navbar')._navbar local args = {} local origArgs local root local function notempty( s ) return s and s:match( '%S' ) end local function fixChildBoxes(sval, tt) if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end -- the next two lines add a newline at the end of lists for the PHP parser -- https://en.wikipedia.org/w/index.php?title=Template_talk:Infobox_musical_artist&oldid=849054481 -- remove when [[:phab:T191516]] is fixed or OBE s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1') s = mw.ustring.gsub(s, '^(%{%|)', '\n%1') return s else return sval end end local function union(t1, t2) -- Returns the union of the values of two tables, as a sequence. local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end local function getArgNums(prefix) -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end local function addRow(rowArgs) -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. if rowArgs.header then root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :attr('id', rowArgs.rowid) :tag('th') :attr('colspan', 2) :attr('id', rowArgs.headerid) :addClass(rowArgs.class) :addClass(args.headerclass) :css('text-align', 'center') :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) if rowArgs.data then root:wikitext('[[Category:Pages which use infobox templates with ignored data cells]]') end elseif rowArgs.data then if not rowArgs.data:gsub('%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]', ''):match('^%S') then rowArgs.rowstyle = 'display:none' end local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) row:attr('id', rowArgs.rowid) if rowArgs.label then row :tag('th') :attr('scope', 'row') :attr('id', rowArgs.labelid) :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') if not rowArgs.label then dataCell :attr('colspan', 2) :css('text-align', 'center') end dataCell :attr('id', rowArgs.dataid) :addClass(rowArgs.class) :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.data, 'td')) end end local function renderTitle() if not args.title then return end root :tag('caption') :addClass(args.titleclass) :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end root :tag('tr') :tag('th') :attr('colspan', 2) :addClass(args.aboveclass) :css('text-align', 'center') :css('font-size', '125%') :css('font-weight', 'bold') :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end root :tag('tr') :tag('td') :attr('colspan', '2') :addClass(args.belowclass) :css('text-align', 'center') :cssText(args.belowstyle) :wikitext(fixChildBoxes(args.below,'td')) end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addRow({ data = args['subheader' .. tostring(num)], datastyle = args.subheaderstyle, rowcellstyle = args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :cssText(args.captionstyle) :wikitext(caption) end addRow({ data = tostring(data), datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end local function renderRows() -- Gets the union of the header and data argument numbers, -- and renders them all in order using addRow. local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)], dataid = args['dataid' .. tostring(num)], labelid = args['labelid' .. tostring(num)], headerid = args['headerid' .. tostring(num)], rowid = args['rowid' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end root :tag('tr') :tag('td') :attr('colspan', '2') :css('text-align', 'right') :wikitext(navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end local function renderTrackingCategories() if args.decat ~= 'yes' then if args.child == 'yes' then if args.title then root:wikitext('[[Category:Pages which use embedded infobox templates with the title parameter]]') end elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') end end end local function _infobox() -- Specify the overall layout of the infobox, with special settings -- if the infobox is used as a 'child' inside another infobox. if args.child ~= 'yes' then root = mw.html.create('table') root :addClass((args.subbox ~= 'yes') and 'infobox' or nil) :addClass(args.bodyclass) if args.subbox == 'yes' then root :css('padding', '0') :css('border', 'none') :css('margin', '-3px') :css('width', 'auto') :css('min-width', '100%') :css('font-size', '100%') :css('clear', 'none') :css('float', 'none') :css('background-color', 'transparent') else root :css('width', '22em') end root :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end renderSubheaders() renderImages() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderTrackingCategories() return tostring(root) end local function preprocessSingleArg(argName) -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end local function preprocessArgs(prefixTable, step) -- Assign the parameters with the given prefixes to the args table, in order, in batches -- of the step size specified. This is to prevent references etc. from appearing in the -- wrong order. The prefixTable should be an array containing tables, each of which has -- two possible fields, a "prefix" string and a "depend" table. The function always parses -- parameters containing the "prefix" string, but only parses parameters in the "depend" -- table if the prefix parameter is present and non-blank. if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then moreArgumentsExist = true -- Do another loop if any arguments are found, even blank ones. preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present and not blank, or -- we are processing "prefix1" and "prefix" is present and not blank, and -- if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end function p.infobox(frame) -- Allow arguments passed via #invoke to override those passed into the invoking template. if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args end for k,v in pairs(frame.args) do origArgs[k] = mw.text.trim(v) end -- Parse the data parameters in the same order that the old {{infobox}} did, so that -- references etc. will display in the expected places. Parameters that depend on -- another parameter are only processed if that parameter is present, to avoid -- phantom references appearing in article reference lists. preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'}, {prefix = 'dataid'}, {prefix = 'labelid'}, {prefix = 'headerid'}, {prefix = 'rowid'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') args['italic title'] = origArgs['italic title'] -- different behaviour if blank or absent preprocessSingleArg('decat') return _infobox() end return p ogerbyi8ry8q8xs4bhtac9l6m6tc5tn 803436 803435 2020-05-16T21:23:56Z en>Ahecht 0 Undid revision 957070706 by [[Special:Contributions/Ahecht|Ahecht]] ([[User talk:Ahecht|talk]]) - Undo for now, not very useful due to parameter name collisions 803436 Scribunto text/plain -- -- This module implements {{Infobox}} -- local p = {} local navbar = require('Module:Navbar')._navbar local args = {} local origArgs local root local function notempty( s ) return s and s:match( '%S' ) end local function fixChildBoxes(sval, tt) if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end -- the next two lines add a newline at the end of lists for the PHP parser -- https://en.wikipedia.org/w/index.php?title=Template_talk:Infobox_musical_artist&oldid=849054481 -- remove when [[:phab:T191516]] is fixed or OBE s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1') s = mw.ustring.gsub(s, '^(%{%|)', '\n%1') return s else return sval end end local function union(t1, t2) -- Returns the union of the values of two tables, as a sequence. local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end local function getArgNums(prefix) -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end local function addRow(rowArgs) -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. if rowArgs.header then root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :attr('id', rowArgs.rowid) :tag('th') :attr('colspan', 2) :attr('id', rowArgs.headerid) :addClass(rowArgs.class) :addClass(args.headerclass) :css('text-align', 'center') :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) if rowArgs.data then root:wikitext('[[Category:Pages which use infobox templates with ignored data cells]]') end elseif rowArgs.data then if not rowArgs.data:gsub('%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]', ''):match('^%S') then rowArgs.rowstyle = 'display:none' end local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) row:attr('id', rowArgs.rowid) if rowArgs.label then row :tag('th') :attr('scope', 'row') :attr('id', rowArgs.labelid) :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') if not rowArgs.label then dataCell :attr('colspan', 2) :css('text-align', 'center') end dataCell :attr('id', rowArgs.dataid) :addClass(rowArgs.class) :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.data, 'td')) end end local function renderTitle() if not args.title then return end root :tag('caption') :addClass(args.titleclass) :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end root :tag('tr') :tag('th') :attr('colspan', 2) :addClass(args.aboveclass) :css('text-align', 'center') :css('font-size', '125%') :css('font-weight', 'bold') :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end root :tag('tr') :tag('td') :attr('colspan', '2') :addClass(args.belowclass) :css('text-align', 'center') :cssText(args.belowstyle) :wikitext(fixChildBoxes(args.below,'td')) end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addRow({ data = args['subheader' .. tostring(num)], datastyle = args.subheaderstyle, rowcellstyle = args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :cssText(args.captionstyle) :wikitext(caption) end addRow({ data = tostring(data), datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end local function renderRows() -- Gets the union of the header and data argument numbers, -- and renders them all in order using addRow. local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)], dataid = args['dataid' .. tostring(num)], labelid = args['labelid' .. tostring(num)], headerid = args['headerid' .. tostring(num)], rowid = args['rowid' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end root :tag('tr') :tag('td') :attr('colspan', '2') :css('text-align', 'right') :wikitext(navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end local function renderTrackingCategories() if args.decat ~= 'yes' then if args.child == 'yes' then if args.title then root:wikitext('[[Category:Pages which use embedded infobox templates with the title parameter]]') end elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') end end end local function _infobox() -- Specify the overall layout of the infobox, with special settings -- if the infobox is used as a 'child' inside another infobox. if args.child ~= 'yes' then root = mw.html.create('table') root :addClass((args.subbox ~= 'yes') and 'infobox' or nil) :addClass(args.bodyclass) if args.subbox == 'yes' then root :css('padding', '0') :css('border', 'none') :css('margin', '-3px') :css('width', 'auto') :css('min-width', '100%') :css('font-size', '100%') :css('clear', 'none') :css('float', 'none') :css('background-color', 'transparent') else root :css('width', '22em') end root :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end renderSubheaders() renderImages() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderTrackingCategories() return tostring(root) end local function preprocessSingleArg(argName) -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end local function preprocessArgs(prefixTable, step) -- Assign the parameters with the given prefixes to the args table, in order, in batches -- of the step size specified. This is to prevent references etc. from appearing in the -- wrong order. The prefixTable should be an array containing tables, each of which has -- two possible fields, a "prefix" string and a "depend" table. The function always parses -- parameters containing the "prefix" string, but only parses parameters in the "depend" -- table if the prefix parameter is present and non-blank. if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then moreArgumentsExist = true -- Do another loop if any arguments are found, even blank ones. preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present and not blank, or -- we are processing "prefix1" and "prefix" is present and not blank, and -- if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end function p.infobox(frame) -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end -- Parse the data parameters in the same order that the old {{infobox}} did, so that -- references etc. will display in the expected places. Parameters that depend on -- another parameter are only processed if that parameter is present, to avoid -- phantom references appearing in article reference lists. preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'}, {prefix = 'dataid'}, {prefix = 'labelid'}, {prefix = 'headerid'}, {prefix = 'rowid'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') args['italic title'] = origArgs['italic title'] -- different behaviour if blank or absent preprocessSingleArg('decat') return _infobox() end return p 7j4mp6z10ropygjyub7mtxdrh0n1rn3 803437 803436 2020-05-16T21:37:05Z en>Ahecht 0 sync p.infoboxTemplate(frame) from sandbox 803437 Scribunto text/plain -- -- This module implements {{Infobox}} -- local p = {} local navbar = require('Module:Navbar')._navbar local args = {} local origArgs local root local function notempty( s ) return s and s:match( '%S' ) end local function fixChildBoxes(sval, tt) if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end -- the next two lines add a newline at the end of lists for the PHP parser -- https://en.wikipedia.org/w/index.php?title=Template_talk:Infobox_musical_artist&oldid=849054481 -- remove when [[:phab:T191516]] is fixed or OBE s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1') s = mw.ustring.gsub(s, '^(%{%|)', '\n%1') return s else return sval end end local function union(t1, t2) -- Returns the union of the values of two tables, as a sequence. local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end local function getArgNums(prefix) -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end local function addRow(rowArgs) -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. if rowArgs.header then root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :attr('id', rowArgs.rowid) :tag('th') :attr('colspan', 2) :attr('id', rowArgs.headerid) :addClass(rowArgs.class) :addClass(args.headerclass) :css('text-align', 'center') :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) if rowArgs.data then root:wikitext('[[Category:Pages which use infobox templates with ignored data cells]]') end elseif rowArgs.data then if not rowArgs.data:gsub('%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]', ''):match('^%S') then rowArgs.rowstyle = 'display:none' end local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) row:attr('id', rowArgs.rowid) if rowArgs.label then row :tag('th') :attr('scope', 'row') :attr('id', rowArgs.labelid) :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') if not rowArgs.label then dataCell :attr('colspan', 2) :css('text-align', 'center') end dataCell :attr('id', rowArgs.dataid) :addClass(rowArgs.class) :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.data, 'td')) end end local function renderTitle() if not args.title then return end root :tag('caption') :addClass(args.titleclass) :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end root :tag('tr') :tag('th') :attr('colspan', 2) :addClass(args.aboveclass) :css('text-align', 'center') :css('font-size', '125%') :css('font-weight', 'bold') :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end root :tag('tr') :tag('td') :attr('colspan', '2') :addClass(args.belowclass) :css('text-align', 'center') :cssText(args.belowstyle) :wikitext(fixChildBoxes(args.below,'td')) end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addRow({ data = args['subheader' .. tostring(num)], datastyle = args.subheaderstyle, rowcellstyle = args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :cssText(args.captionstyle) :wikitext(caption) end addRow({ data = tostring(data), datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end local function renderRows() -- Gets the union of the header and data argument numbers, -- and renders them all in order using addRow. local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)], dataid = args['dataid' .. tostring(num)], labelid = args['labelid' .. tostring(num)], headerid = args['headerid' .. tostring(num)], rowid = args['rowid' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end root :tag('tr') :tag('td') :attr('colspan', '2') :css('text-align', 'right') :wikitext(navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end local function renderTrackingCategories() if args.decat ~= 'yes' then if args.child == 'yes' then if args.title then root:wikitext('[[Category:Pages which use embedded infobox templates with the title parameter]]') end elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') end end end local function _infobox() -- Specify the overall layout of the infobox, with special settings -- if the infobox is used as a 'child' inside another infobox. if args.child ~= 'yes' then root = mw.html.create('table') root :addClass((args.subbox ~= 'yes') and 'infobox' or nil) :addClass(args.bodyclass) if args.subbox == 'yes' then root :css('padding', '0') :css('border', 'none') :css('margin', '-3px') :css('width', 'auto') :css('min-width', '100%') :css('font-size', '100%') :css('clear', 'none') :css('float', 'none') :css('background-color', 'transparent') else root :css('width', '22em') end root :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end renderSubheaders() renderImages() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderTrackingCategories() return tostring(root) end local function preprocessSingleArg(argName) -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end local function preprocessArgs(prefixTable, step) -- Assign the parameters with the given prefixes to the args table, in order, in batches -- of the step size specified. This is to prevent references etc. from appearing in the -- wrong order. The prefixTable should be an array containing tables, each of which has -- two possible fields, a "prefix" string and a "depend" table. The function always parses -- parameters containing the "prefix" string, but only parses parameters in the "depend" -- table if the prefix parameter is present and non-blank. if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then moreArgumentsExist = true -- Do another loop if any arguments are found, even blank ones. preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present and not blank, or -- we are processing "prefix1" and "prefix" is present and not blank, and -- if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end function p.parseDataParameters() -- Parse the data parameters in the same order that the old {{infobox}} did, so that -- references etc. will display in the expected places. Parameters that depend on -- another parameter are only processed if that parameter is present, to avoid -- phantom references appearing in article reference lists. preprocessSingleArg('autoheaders') preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'}, {prefix = 'dataid'}, {prefix = 'labelid'}, {prefix = 'headerid'}, {prefix = 'rowid'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') args['italic title'] = origArgs['italic title'] -- different behaviour if blank or absent preprocessSingleArg('decat') end function p.infobox(frame) -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame.args end p.parseDataParameters() return _infobox() end function p.infoboxTemplate(frame) -- For calling via #invoke within a template origArgs = {} for k,v in pairs(frame.args) do origArgs[k] = mw.text.trim(v) end p.parseDataParameters() return _infobox() end return p sphu312gk4rzj6aj5um0bkon03s0g5g 803438 803437 2020-05-16T23:04:02Z en>Ahecht 0 fix Attempt to index upvalue 'origArgs' error 803438 Scribunto text/plain -- -- This module implements {{Infobox}} -- local p = {} local navbar = require('Module:Navbar')._navbar local args = {} local origArgs = {} local root local function notempty( s ) return s and s:match( '%S' ) end local function fixChildBoxes(sval, tt) if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end -- the next two lines add a newline at the end of lists for the PHP parser -- https://en.wikipedia.org/w/index.php?title=Template_talk:Infobox_musical_artist&oldid=849054481 -- remove when [[:phab:T191516]] is fixed or OBE s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1') s = mw.ustring.gsub(s, '^(%{%|)', '\n%1') return s else return sval end end local function union(t1, t2) -- Returns the union of the values of two tables, as a sequence. local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end local function getArgNums(prefix) -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end local function addRow(rowArgs) -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. if rowArgs.header then root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :attr('id', rowArgs.rowid) :tag('th') :attr('colspan', 2) :attr('id', rowArgs.headerid) :addClass(rowArgs.class) :addClass(args.headerclass) :css('text-align', 'center') :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) if rowArgs.data then root:wikitext('[[Category:Pages which use infobox templates with ignored data cells]]') end elseif rowArgs.data then if not rowArgs.data:gsub('%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]', ''):match('^%S') then rowArgs.rowstyle = 'display:none' end local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) row:attr('id', rowArgs.rowid) if rowArgs.label then row :tag('th') :attr('scope', 'row') :attr('id', rowArgs.labelid) :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') if not rowArgs.label then dataCell :attr('colspan', 2) :css('text-align', 'center') end dataCell :attr('id', rowArgs.dataid) :addClass(rowArgs.class) :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.data, 'td')) end end local function renderTitle() if not args.title then return end root :tag('caption') :addClass(args.titleclass) :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end root :tag('tr') :tag('th') :attr('colspan', 2) :addClass(args.aboveclass) :css('text-align', 'center') :css('font-size', '125%') :css('font-weight', 'bold') :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end root :tag('tr') :tag('td') :attr('colspan', '2') :addClass(args.belowclass) :css('text-align', 'center') :cssText(args.belowstyle) :wikitext(fixChildBoxes(args.below,'td')) end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addRow({ data = args['subheader' .. tostring(num)], datastyle = args.subheaderstyle, rowcellstyle = args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :cssText(args.captionstyle) :wikitext(caption) end addRow({ data = tostring(data), datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end local function renderRows() -- Gets the union of the header and data argument numbers, -- and renders them all in order using addRow. local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)], dataid = args['dataid' .. tostring(num)], labelid = args['labelid' .. tostring(num)], headerid = args['headerid' .. tostring(num)], rowid = args['rowid' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end root :tag('tr') :tag('td') :attr('colspan', '2') :css('text-align', 'right') :wikitext(navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end local function renderTrackingCategories() if args.decat ~= 'yes' then if args.child == 'yes' then if args.title then root:wikitext('[[Category:Pages which use embedded infobox templates with the title parameter]]') end elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') end end end local function _infobox() -- Specify the overall layout of the infobox, with special settings -- if the infobox is used as a 'child' inside another infobox. if args.child ~= 'yes' then root = mw.html.create('table') root :addClass((args.subbox ~= 'yes') and 'infobox' or nil) :addClass(args.bodyclass) if args.subbox == 'yes' then root :css('padding', '0') :css('border', 'none') :css('margin', '-3px') :css('width', 'auto') :css('min-width', '100%') :css('font-size', '100%') :css('clear', 'none') :css('float', 'none') :css('background-color', 'transparent') else root :css('width', '22em') end root :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end renderSubheaders() renderImages() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderTrackingCategories() return tostring(root) end local function preprocessSingleArg(argName) -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end local function preprocessArgs(prefixTable, step) -- Assign the parameters with the given prefixes to the args table, in order, in batches -- of the step size specified. This is to prevent references etc. from appearing in the -- wrong order. The prefixTable should be an array containing tables, each of which has -- two possible fields, a "prefix" string and a "depend" table. The function always parses -- parameters containing the "prefix" string, but only parses parameters in the "depend" -- table if the prefix parameter is present and non-blank. if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then moreArgumentsExist = true -- Do another loop if any arguments are found, even blank ones. preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present and not blank, or -- we are processing "prefix1" and "prefix" is present and not blank, and -- if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end function p.parseDataParameters() -- Parse the data parameters in the same order that the old {{infobox}} did, so that -- references etc. will display in the expected places. Parameters that depend on -- another parameter are only processed if that parameter is present, to avoid -- phantom references appearing in article reference lists. preprocessSingleArg('autoheaders') preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'}, {prefix = 'dataid'}, {prefix = 'labelid'}, {prefix = 'headerid'}, {prefix = 'rowid'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') args['italic title'] = origArgs['italic title'] -- different behaviour if blank or absent preprocessSingleArg('decat') end function p.infobox(frame) -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame.args end p.parseDataParameters() return _infobox() end function p.infoboxTemplate(frame) -- For calling via #invoke within a template origArgs = {} for k,v in pairs(frame.args) do origArgs[k] = mw.text.trim(v) end p.parseDataParameters() return _infobox() end return p dgvpu6td21pdgys9xxz1582nb7k90de 803439 803438 2020-05-16T23:07:44Z en>Ahecht 0 Fix "attempt to index upvalue 'origArgs' (a nil value)" 803439 Scribunto text/plain -- -- This module implements {{Infobox}} -- local p = {} local navbar = require('Module:Navbar')._navbar local args = {} local origArgs = {} local root local function notempty( s ) return s and s:match( '%S' ) end local function fixChildBoxes(sval, tt) if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end -- the next two lines add a newline at the end of lists for the PHP parser -- https://en.wikipedia.org/w/index.php?title=Template_talk:Infobox_musical_artist&oldid=849054481 -- remove when [[:phab:T191516]] is fixed or OBE s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1') s = mw.ustring.gsub(s, '^(%{%|)', '\n%1') return s else return sval end end local function union(t1, t2) -- Returns the union of the values of two tables, as a sequence. local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end local function getArgNums(prefix) -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end local function addRow(rowArgs) -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. if rowArgs.header then root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :attr('id', rowArgs.rowid) :tag('th') :attr('colspan', 2) :attr('id', rowArgs.headerid) :addClass(rowArgs.class) :addClass(args.headerclass) :css('text-align', 'center') :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) if rowArgs.data then root:wikitext('[[Category:Pages which use infobox templates with ignored data cells]]') end elseif rowArgs.data then if not rowArgs.data:gsub('%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]', ''):match('^%S') then rowArgs.rowstyle = 'display:none' end local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) row:attr('id', rowArgs.rowid) if rowArgs.label then row :tag('th') :attr('scope', 'row') :attr('id', rowArgs.labelid) :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') if not rowArgs.label then dataCell :attr('colspan', 2) :css('text-align', 'center') end dataCell :attr('id', rowArgs.dataid) :addClass(rowArgs.class) :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.data, 'td')) end end local function renderTitle() if not args.title then return end root :tag('caption') :addClass(args.titleclass) :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end root :tag('tr') :tag('th') :attr('colspan', 2) :addClass(args.aboveclass) :css('text-align', 'center') :css('font-size', '125%') :css('font-weight', 'bold') :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end root :tag('tr') :tag('td') :attr('colspan', '2') :addClass(args.belowclass) :css('text-align', 'center') :cssText(args.belowstyle) :wikitext(fixChildBoxes(args.below,'td')) end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addRow({ data = args['subheader' .. tostring(num)], datastyle = args.subheaderstyle, rowcellstyle = args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :cssText(args.captionstyle) :wikitext(caption) end addRow({ data = tostring(data), datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end local function renderRows() -- Gets the union of the header and data argument numbers, -- and renders them all in order using addRow. local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)], dataid = args['dataid' .. tostring(num)], labelid = args['labelid' .. tostring(num)], headerid = args['headerid' .. tostring(num)], rowid = args['rowid' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end root :tag('tr') :tag('td') :attr('colspan', '2') :css('text-align', 'right') :wikitext(navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end local function renderTrackingCategories() if args.decat ~= 'yes' then if args.child == 'yes' then if args.title then root:wikitext('[[Category:Pages which use embedded infobox templates with the title parameter]]') end elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') end end end local function _infobox() -- Specify the overall layout of the infobox, with special settings -- if the infobox is used as a 'child' inside another infobox. if args.child ~= 'yes' then root = mw.html.create('table') root :addClass((args.subbox ~= 'yes') and 'infobox' or nil) :addClass(args.bodyclass) if args.subbox == 'yes' then root :css('padding', '0') :css('border', 'none') :css('margin', '-3px') :css('width', 'auto') :css('min-width', '100%') :css('font-size', '100%') :css('clear', 'none') :css('float', 'none') :css('background-color', 'transparent') else root :css('width', '22em') end root :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end renderSubheaders() renderImages() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderTrackingCategories() return tostring(root) end local function preprocessSingleArg(argName) -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end local function preprocessArgs(prefixTable, step) -- Assign the parameters with the given prefixes to the args table, in order, in batches -- of the step size specified. This is to prevent references etc. from appearing in the -- wrong order. The prefixTable should be an array containing tables, each of which has -- two possible fields, a "prefix" string and a "depend" table. The function always parses -- parameters containing the "prefix" string, but only parses parameters in the "depend" -- table if the prefix parameter is present and non-blank. if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then moreArgumentsExist = true -- Do another loop if any arguments are found, even blank ones. preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present and not blank, or -- we are processing "prefix1" and "prefix" is present and not blank, and -- if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end local function parseDataParameters() -- Parse the data parameters in the same order that the old {{infobox}} did, so that -- references etc. will display in the expected places. Parameters that depend on -- another parameter are only processed if that parameter is present, to avoid -- phantom references appearing in article reference lists. preprocessSingleArg('autoheaders') preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'}, {prefix = 'dataid'}, {prefix = 'labelid'}, {prefix = 'headerid'}, {prefix = 'rowid'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') args['italic title'] = origArgs['italic title'] -- different behaviour if blank or absent preprocessSingleArg('decat') end function p.infobox(frame) -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end parseDataParameters() return _infobox() end function p.infoboxTemplate(frame) -- For calling via #invoke within a template origArgs = {} for k,v in pairs(frame.args) do origArgs[k] = mw.text.trim(v) end parseDataParameters() return _infobox() end return p rqslxfr0ixpm41etqqc9cd2udbuwv4y 803440 803439 2020-06-25T19:04:15Z en>Frietjes 0 per discussion on the talkpage, now supports |autoheaders=y 803440 Scribunto text/plain -- -- This module implements {{Infobox}} -- local p = {} local navbar = require('Module:Navbar')._navbar local args = {} local origArgs = {} local root local function notempty( s ) return s and s:match( '%S' ) end local function fixChildBoxes(sval, tt) if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end -- the next two lines add a newline at the end of lists for the PHP parser -- https://en.wikipedia.org/w/index.php?title=Template_talk:Infobox_musical_artist&oldid=849054481 -- remove when [[:phab:T191516]] is fixed or OBE s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1') s = mw.ustring.gsub(s, '^(%{%|)', '\n%1') return s else return sval end end local function union(t1, t2) -- Returns the union of the values of two tables, as a sequence. local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end local function getArgNums(prefix) -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end local function addRow(rowArgs) -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. if rowArgs.header and rowArgs.header ~= '_BLANK_' then root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :attr('id', rowArgs.rowid) :tag('th') :attr('colspan', 2) :attr('id', rowArgs.headerid) :addClass(rowArgs.class) :addClass(args.headerclass) :css('text-align', 'center') :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) if rowArgs.data then root:wikitext('[[Category:Pages which use infobox templates with ignored data cells]]') end elseif rowArgs.data then if not rowArgs.data:gsub('%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]', ''):match('^%S') then rowArgs.rowstyle = 'display:none' end local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) row:attr('id', rowArgs.rowid) if rowArgs.label then row :tag('th') :attr('scope', 'row') :attr('id', rowArgs.labelid) :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') if not rowArgs.label then dataCell :attr('colspan', 2) :css('text-align', 'center') end dataCell :attr('id', rowArgs.dataid) :addClass(rowArgs.class) :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.data, 'td')) end end local function renderTitle() if not args.title then return end root :tag('caption') :addClass(args.titleclass) :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end root :tag('tr') :tag('th') :attr('colspan', 2) :addClass(args.aboveclass) :css('text-align', 'center') :css('font-size', '125%') :css('font-weight', 'bold') :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end root :tag('tr') :tag('td') :attr('colspan', '2') :addClass(args.belowclass) :css('text-align', 'center') :cssText(args.belowstyle) :wikitext(fixChildBoxes(args.below,'td')) end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addRow({ data = args['subheader' .. tostring(num)], datastyle = args.subheaderstyle, rowcellstyle = args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :cssText(args.captionstyle) :wikitext(caption) end addRow({ data = tostring(data), datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end local function preprocessRows() -- Gets the union of the header and data argument numbers, -- and renders them all in order using addRow. local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) local lastheader for k, num in ipairs(rownums) do if args['header' .. tostring(num)] then if lastheader then args['header' .. tostring(lastheader)] = nil end lastheader = num elseif args['data' .. tostring(num)] and args['data' .. tostring(num)]:gsub('%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]', ''):match('^%S') then local data = args['data' .. tostring(num)] if data:gsub('%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]', ''):match('%S') then lastheader = nil end end end if lastheader then args['header' .. tostring(lastheader)] = nil end end local function renderRows() -- Gets the union of the header and data argument numbers, -- and renders them all in order using addRow. local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)], dataid = args['dataid' .. tostring(num)], labelid = args['labelid' .. tostring(num)], headerid = args['headerid' .. tostring(num)], rowid = args['rowid' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end root :tag('tr') :tag('td') :attr('colspan', '2') :css('text-align', 'right') :wikitext(navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end local function renderTrackingCategories() if args.decat ~= 'yes' then if args.child == 'yes' then if args.title then root:wikitext('[[Category:Pages which use embedded infobox templates with the title parameter]]') end elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') end end end local function _infobox() -- Specify the overall layout of the infobox, with special settings -- if the infobox is used as a 'child' inside another infobox. if args.child ~= 'yes' then root = mw.html.create('table') root :addClass((args.subbox ~= 'yes') and 'infobox' or nil) :addClass(args.bodyclass) if args.subbox == 'yes' then root :css('padding', '0') :css('border', 'none') :css('margin', '-3px') :css('width', 'auto') :css('min-width', '100%') :css('font-size', '100%') :css('clear', 'none') :css('float', 'none') :css('background-color', 'transparent') else root :css('width', '22em') end root :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end renderSubheaders() renderImages() if args.autoheaders then preprocessRows() end renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderTrackingCategories() return tostring(root) end local function preprocessSingleArg(argName) -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end local function preprocessArgs(prefixTable, step) -- Assign the parameters with the given prefixes to the args table, in order, in batches -- of the step size specified. This is to prevent references etc. from appearing in the -- wrong order. The prefixTable should be an array containing tables, each of which has -- two possible fields, a "prefix" string and a "depend" table. The function always parses -- parameters containing the "prefix" string, but only parses parameters in the "depend" -- table if the prefix parameter is present and non-blank. if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then moreArgumentsExist = true -- Do another loop if any arguments are found, even blank ones. preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present and not blank, or -- we are processing "prefix1" and "prefix" is present and not blank, and -- if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end local function parseDataParameters() -- Parse the data parameters in the same order that the old {{infobox}} did, so that -- references etc. will display in the expected places. Parameters that depend on -- another parameter are only processed if that parameter is present, to avoid -- phantom references appearing in article reference lists. preprocessSingleArg('autoheaders') preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'}, {prefix = 'dataid'}, {prefix = 'labelid'}, {prefix = 'headerid'}, {prefix = 'rowid'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') args['italic title'] = origArgs['italic title'] -- different behaviour if blank or absent preprocessSingleArg('decat') end function p.infobox(frame) -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end parseDataParameters() return _infobox() end function p.infoboxTemplate(frame) -- For calling via #invoke within a template origArgs = {} for k,v in pairs(frame.args) do origArgs[k] = mw.text.trim(v) end parseDataParameters() return _infobox() end return p icv7hicbyi1nka7zvdkn0xjgl2xiiia 803441 803440 2021-03-31T19:24:48Z en>Izno 0 add using-infobox tstyles support 803441 Scribunto text/plain local p = {} local args = {} local origArgs = {} local root local empty_row_categories = {} local category_in_empty_row_pattern = '%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]' local function fixChildBoxes(sval, tt) local function notempty( s ) return s and s:match( '%S' ) end if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end -- the next two lines add a newline at the end of lists for the PHP parser -- [[Special:Diff/849054481]] -- remove when [[:phab:T191516]] is fixed or OBE s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1') s = mw.ustring.gsub(s, '^(%{%|)', '\n%1') return s else return sval end end -- Returns the union of the values of two tables, as a sequence. local function union(t1, t2) local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local function getArgNums(prefix) local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. local function addRow(rowArgs) if rowArgs.header and rowArgs.header ~= '_BLANK_' then root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :tag('th') :attr('colspan', '2') :addClass('infobox-header') :addClass(rowArgs.class) :addClass(args.headerclass) -- @deprecated next; target .infobox-<name> .infobox-header :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) if rowArgs.data then root:wikitext( '[[Category:Pages which use infobox templates with ignored data cells]]' ) end elseif rowArgs.data and rowArgs.data:gsub( category_in_empty_row_pattern, '' ):match('^%S') then local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) if rowArgs.label then row :tag('th') :attr('scope', 'row') :addClass('infobox-label') -- @deprecated next; target .infobox-<name> .infobox-label :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') dataCell :attr('colspan', not rowArgs.label and '2' or nil) :addClass(not rowArgs.label and 'infobox-full-data' or 'infobox-data') :addClass(rowArgs.class) -- @deprecated next; target .infobox-<name> .infobox(-full)-data :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.data, 'td')) else table.insert(empty_row_categories, rowArgs.data or '') end end local function renderTitle() if not args.title then return end root :tag('caption') :addClass('infobox-title') :addClass(args.titleclass) -- @deprecated next; target .infobox-<name> .infobox-title :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end root :tag('tr') :tag('th') :attr('colspan', '2') :addClass('infobox-above') :addClass(args.aboveclass) -- @deprecated next; target .infobox-<name> .infobox-above :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end root :tag('tr') :tag('td') :attr('colspan', '2') :addClass('infobox-below') :addClass(args.belowclass) -- @deprecated next; target .infobox-<name> .infobox-below :cssText(args.belowstyle) :wikitext(fixChildBoxes(args.below,'td')) end local function addSubheaderRow(subheaderArgs) if subheaderArgs.data and subheaderArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then local row = root:tag('tr') row:addClass(subheaderArgs.rowclass) local dataCell = row:tag('td') dataCell :attr('colspan', '2') :addClass('infobox-subheader') :addClass(subheaderArgs.class) :cssText(subheaderArgs.datastyle) :cssText(subheaderArgs.rowcellstyle) :wikitext(fixChildBoxes(subheaderArgs.data, 'td')) else table.insert(empty_row_categories, subheaderArgs.data or '') end end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addSubheaderRow({ data = args['subheader' .. tostring(num)], -- @deprecated next; target .infobox-<name> .infobox-subheader datastyle = args.subheaderstyle, rowcellstyle = args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function addImageRow(imageArgs) if imageArgs.data and imageArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then local row = root:tag('tr') row:addClass(imageArgs.rowclass) local dataCell = row:tag('td') dataCell :attr('colspan', '2') :addClass('infobox-image') :addClass(imageArgs.class) :cssText(imageArgs.datastyle) :wikitext(fixChildBoxes(imageArgs.data, 'td')) else table.insert(empty_row_categories, imageArgs.data or '') end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :addClass('infobox-caption') -- @deprecated next; target .infobox-<name> .infobox-caption :cssText(args.captionstyle) :wikitext(caption) end addImageRow({ data = tostring(data), -- @deprecated next; target .infobox-<name> .infobox-image datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end -- When autoheaders are turned on, preprocesses the rows local function preprocessRows() if not args.autoheaders then return end local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) local lastheader for k, num in ipairs(rownums) do if args['header' .. tostring(num)] then if lastheader then args['header' .. tostring(lastheader)] = nil end lastheader = num elseif args['data' .. tostring(num)] and args['data' .. tostring(num)]:gsub( category_in_empty_row_pattern, '' ):match('^%S') then local data = args['data' .. tostring(num)] if data:gsub(category_in_empty_row_pattern, ''):match('%S') then lastheader = nil end end end if lastheader then args['header' .. tostring(lastheader)] = nil end end -- Gets the union of the header and data argument numbers, -- and renders them all in order local function renderRows() local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], -- @deprecated next; target .infobox-<name> rowclass rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end root :tag('tr') :tag('td') :attr('colspan', '2') :addClass('infobox-navbar') :wikitext(require('Module:Navbar')._navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end -- Categories in otherwise empty rows are collected in empty_row_categories. -- This function adds them to the module output. It is not affected by -- args.decat because this module should not prevent module-external categories -- from rendering. local function renderEmptyRowCategories() for _, s in ipairs(empty_row_categories) do root:wikitext(s) end end -- Render tracking categories. args.decat == turns off tracking categories. local function renderTrackingCategories() if args.decat == 'yes' then return end if args.child == 'yes' then if args.title then root:wikitext( '[[Category:Pages which use embedded infobox templates with the title parameter]]' ) end elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') end end --[=[ Loads the templatestyles for the infobox. TODO: load base templatestyles here rather than in MediaWiki:Common.css We aren't doing it here yet because there are 4-5000 pages with 'raw' infobox tables. See [[Mediawiki_talk:Common.css/to_do#Infobox]] and/or come help :). When we do this we should clean up the inline CSS below too. Will have to do some bizarre conversion category like with sidebar. ]=] local function loadTemplateStyles() local frame = mw.getCurrentFrame() -- See function description -- local base_templatestyles = frame:extensionTag{ -- name = 'templatestyles', args = { src = cfg.i18n.templatestyles } -- } local templatestyles = '' if args['templatestyles'] then templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['templatestyles'] } } end local child_templatestyles = '' if args['child templatestyles'] then child_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['child templatestyles'] } } end local grandchild_templatestyles = '' if args['grandchild templatestyles'] then grandchild_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['grandchild templatestyles'] } } end return table.concat({ -- base_templatestyles, -- see function description templatestyles, child_templatestyles, grandchild_templatestyles }) end -- Specify the overall layout of the infobox, with special settings if the -- infobox is used as a 'child' inside another infobox. local function _infobox() if args.child ~= 'yes' then root = mw.html.create('table') root :addClass(args.subbox == 'yes' and 'infobox-subbox' or 'infobox') :addClass(args.bodyclass) -- @deprecated next; target .infobox-<name> :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end renderSubheaders() renderImages() preprocessRows() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderEmptyRowCategories() renderTrackingCategories() return loadTemplateStyles() .. tostring(root) end -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. local function preprocessSingleArg(argName) if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end -- Assign the parameters with the given prefixes to the args table, in order, in -- batches of the step size specified. This is to prevent references etc. from -- appearing in the wrong order. The prefixTable should be an array containing -- tables, each of which has two possible fields, a "prefix" string and a -- "depend" table. The function always parses parameters containing the "prefix" -- string, but only parses parameters in the "depend" table if the prefix -- parameter is present and non-blank. local function preprocessArgs(prefixTable, step) if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present -- and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then -- Do another loop if any arguments are found, even blank ones. moreArgumentsExist = true preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present -- and not blank, or we are processing "prefix1" and "prefix" is -- present and not blank, and if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end -- Parse the data parameters in the same order that the old {{infobox}} did, so -- that references etc. will display in the expected places. Parameters that -- depend on another parameter are only processed if that parameter is present, -- to avoid phantom references appearing in article reference lists. local function parseDataParameters() preprocessSingleArg('autoheaders') preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') -- different behaviour for italics if blank or absent args['italic title'] = origArgs['italic title'] preprocessSingleArg('decat') preprocessSingleArg('templatestyles') preprocessSingleArg('child templatestyles') preprocessSingleArg('grandchild templatestyles') end -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. function p.infobox(frame) if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end parseDataParameters() return _infobox() end -- For calling via #invoke within a template function p.infoboxTemplate(frame) origArgs = {} for k,v in pairs(frame.args) do origArgs[k] = mw.text.trim(v) end parseDataParameters() return _infobox() end return p e3cfyoal7a38ou4omgcpf2j7vsh8hq2 803442 803441 2021-07-12T21:56:48Z en>Frietjes 0 fix for {{infobox}} with no input args generating an empty <table class="infobox"></table> per talk page 803442 Scribunto text/plain local p = {} local args = {} local origArgs = {} local root local empty_row_categories = {} local category_in_empty_row_pattern = '%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]' local has_rows = false local function fixChildBoxes(sval, tt) local function notempty( s ) return s and s:match( '%S' ) end if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end -- the next two lines add a newline at the end of lists for the PHP parser -- [[Special:Diff/849054481]] -- remove when [[:phab:T191516]] is fixed or OBE s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1') s = mw.ustring.gsub(s, '^(%{%|)', '\n%1') return s else return sval end end -- Cleans empty tables local function cleanInfobox() root = tostring(root) if has_rows == false then root = mw.ustring.gsub(root, '<table[^<>]*>%s*</table>', '') end end -- Returns the union of the values of two tables, as a sequence. local function union(t1, t2) local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local function getArgNums(prefix) local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. local function addRow(rowArgs) if rowArgs.header and rowArgs.header ~= '_BLANK_' then has_rows = true root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :tag('th') :attr('colspan', '2') :addClass('infobox-header') :addClass(rowArgs.class) :addClass(args.headerclass) -- @deprecated next; target .infobox-<name> .infobox-header :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) if rowArgs.data then root:wikitext( '[[Category:Pages which use infobox templates with ignored data cells]]' ) end elseif rowArgs.data and rowArgs.data:gsub( category_in_empty_row_pattern, '' ):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) if rowArgs.label then row :tag('th') :attr('scope', 'row') :addClass('infobox-label') -- @deprecated next; target .infobox-<name> .infobox-label :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') dataCell :attr('colspan', not rowArgs.label and '2' or nil) :addClass(not rowArgs.label and 'infobox-full-data' or 'infobox-data') :addClass(rowArgs.class) -- @deprecated next; target .infobox-<name> .infobox(-full)-data :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.data, 'td')) else table.insert(empty_row_categories, rowArgs.data or '') end end local function renderTitle() if not args.title then return end has_rows = true root :tag('caption') :addClass('infobox-title') :addClass(args.titleclass) -- @deprecated next; target .infobox-<name> .infobox-title :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end has_rows = true root :tag('tr') :tag('th') :attr('colspan', '2') :addClass('infobox-above') :addClass(args.aboveclass) -- @deprecated next; target .infobox-<name> .infobox-above :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end has_rows = true root :tag('tr') :tag('td') :attr('colspan', '2') :addClass('infobox-below') :addClass(args.belowclass) -- @deprecated next; target .infobox-<name> .infobox-below :cssText(args.belowstyle) :wikitext(fixChildBoxes(args.below,'td')) end local function addSubheaderRow(subheaderArgs) if subheaderArgs.data and subheaderArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(subheaderArgs.rowclass) local dataCell = row:tag('td') dataCell :attr('colspan', '2') :addClass('infobox-subheader') :addClass(subheaderArgs.class) :cssText(subheaderArgs.datastyle) :cssText(subheaderArgs.rowcellstyle) :wikitext(fixChildBoxes(subheaderArgs.data, 'td')) else table.insert(empty_row_categories, subheaderArgs.data or '') end end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addSubheaderRow({ data = args['subheader' .. tostring(num)], -- @deprecated next; target .infobox-<name> .infobox-subheader datastyle = args.subheaderstyle, rowcellstyle = args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function addImageRow(imageArgs) if imageArgs.data and imageArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(imageArgs.rowclass) local dataCell = row:tag('td') dataCell :attr('colspan', '2') :addClass('infobox-image') :addClass(imageArgs.class) :cssText(imageArgs.datastyle) :wikitext(fixChildBoxes(imageArgs.data, 'td')) else table.insert(empty_row_categories, imageArgs.data or '') end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :addClass('infobox-caption') -- @deprecated next; target .infobox-<name> .infobox-caption :cssText(args.captionstyle) :wikitext(caption) end addImageRow({ data = tostring(data), -- @deprecated next; target .infobox-<name> .infobox-image datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end -- When autoheaders are turned on, preprocesses the rows local function preprocessRows() if not args.autoheaders then return end local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) local lastheader for k, num in ipairs(rownums) do if args['header' .. tostring(num)] then if lastheader then args['header' .. tostring(lastheader)] = nil end lastheader = num elseif args['data' .. tostring(num)] and args['data' .. tostring(num)]:gsub( category_in_empty_row_pattern, '' ):match('^%S') then local data = args['data' .. tostring(num)] if data:gsub(category_in_empty_row_pattern, ''):match('%S') then lastheader = nil end end end if lastheader then args['header' .. tostring(lastheader)] = nil end end -- Gets the union of the header and data argument numbers, -- and renders them all in order local function renderRows() local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], -- @deprecated next; target .infobox-<name> rowclass rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end has_rows = true root :tag('tr') :tag('td') :attr('colspan', '2') :addClass('infobox-navbar') :wikitext(require('Module:Navbar')._navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end -- Categories in otherwise empty rows are collected in empty_row_categories. -- This function adds them to the module output. It is not affected by -- args.decat because this module should not prevent module-external categories -- from rendering. local function renderEmptyRowCategories() for _, s in ipairs(empty_row_categories) do root:wikitext(s) end end -- Render tracking categories. args.decat == turns off tracking categories. local function renderTrackingCategories() if args.decat == 'yes' then return end if args.child == 'yes' then if args.title then root:wikitext( '[[Category:Pages which use embedded infobox templates with the title parameter]]' ) end elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') end end --[=[ Loads the templatestyles for the infobox. TODO: load base templatestyles here rather than in MediaWiki:Common.css We aren't doing it here yet because there are 4-5000 pages with 'raw' infobox tables. See [[Mediawiki_talk:Common.css/to_do#Infobox]] and/or come help :). When we do this we should clean up the inline CSS below too. Will have to do some bizarre conversion category like with sidebar. ]=] local function loadTemplateStyles() local frame = mw.getCurrentFrame() -- See function description -- local base_templatestyles = frame:extensionTag{ -- name = 'templatestyles', args = { src = cfg.i18n.templatestyles } -- } local templatestyles = '' if args['templatestyles'] then templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['templatestyles'] } } end local child_templatestyles = '' if args['child templatestyles'] then child_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['child templatestyles'] } } end local grandchild_templatestyles = '' if args['grandchild templatestyles'] then grandchild_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['grandchild templatestyles'] } } end return table.concat({ -- base_templatestyles, -- see function description templatestyles, child_templatestyles, grandchild_templatestyles }) end -- Specify the overall layout of the infobox, with special settings if the -- infobox is used as a 'child' inside another infobox. local function _infobox() if args.child ~= 'yes' then root = mw.html.create('table') root :addClass(args.subbox == 'yes' and 'infobox-subbox' or 'infobox') :addClass(args.bodyclass) -- @deprecated next; target .infobox-<name> :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end renderSubheaders() renderImages() preprocessRows() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderEmptyRowCategories() renderTrackingCategories() cleanInfobox() return loadTemplateStyles() .. root end -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. local function preprocessSingleArg(argName) if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end -- Assign the parameters with the given prefixes to the args table, in order, in -- batches of the step size specified. This is to prevent references etc. from -- appearing in the wrong order. The prefixTable should be an array containing -- tables, each of which has two possible fields, a "prefix" string and a -- "depend" table. The function always parses parameters containing the "prefix" -- string, but only parses parameters in the "depend" table if the prefix -- parameter is present and non-blank. local function preprocessArgs(prefixTable, step) if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present -- and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then -- Do another loop if any arguments are found, even blank ones. moreArgumentsExist = true preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present -- and not blank, or we are processing "prefix1" and "prefix" is -- present and not blank, and if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end -- Parse the data parameters in the same order that the old {{infobox}} did, so -- that references etc. will display in the expected places. Parameters that -- depend on another parameter are only processed if that parameter is present, -- to avoid phantom references appearing in article reference lists. local function parseDataParameters() preprocessSingleArg('autoheaders') preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') -- different behaviour for italics if blank or absent args['italic title'] = origArgs['italic title'] preprocessSingleArg('decat') preprocessSingleArg('templatestyles') preprocessSingleArg('child templatestyles') preprocessSingleArg('grandchild templatestyles') end -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. function p.infobox(frame) if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end parseDataParameters() return _infobox() end -- For calling via #invoke within a template function p.infoboxTemplate(frame) origArgs = {} for k,v in pairs(frame.args) do origArgs[k] = mw.text.trim(v) end parseDataParameters() return _infobox() end return p gjmhtnj4qfymzr0fe6nr73pcgags5bu 803443 803442 2021-09-08T18:11:29Z en>Izno 0 nix 803443 Scribunto text/plain local p = {} local args = {} local origArgs = {} local root local empty_row_categories = {} local category_in_empty_row_pattern = '%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]' local has_rows = false local function fixChildBoxes(sval, tt) local function notempty( s ) return s and s:match( '%S' ) end if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end -- the next two lines add a newline at the end of lists for the PHP parser -- [[Special:Diff/849054481]] -- remove when [[:phab:T191516]] is fixed or OBE s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1') s = mw.ustring.gsub(s, '^(%{%|)', '\n%1') return s else return sval end end -- Cleans empty tables local function cleanInfobox() root = tostring(root) if has_rows == false then root = mw.ustring.gsub(root, '<table[^<>]*>%s*</table>', '') end end -- Returns the union of the values of two tables, as a sequence. local function union(t1, t2) local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local function getArgNums(prefix) local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. local function addRow(rowArgs) if rowArgs.header and rowArgs.header ~= '_BLANK_' then has_rows = true root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :tag('th') :attr('colspan', '2') :addClass('infobox-header') :addClass(rowArgs.class) :addClass(args.headerclass) -- @deprecated next; target .infobox-<name> .infobox-header :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) if rowArgs.data then root:wikitext( '[[Category:Pages which use infobox templates with ignored data cells]]' ) end elseif rowArgs.data and rowArgs.data:gsub( category_in_empty_row_pattern, '' ):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) if rowArgs.label then row :tag('th') :attr('scope', 'row') :addClass('infobox-label') -- @deprecated next; target .infobox-<name> .infobox-label :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') dataCell :attr('colspan', not rowArgs.label and '2' or nil) :addClass(not rowArgs.label and 'infobox-full-data' or 'infobox-data') :addClass(rowArgs.class) -- @deprecated next; target .infobox-<name> .infobox(-full)-data :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.data, 'td')) else table.insert(empty_row_categories, rowArgs.data or '') end end local function renderTitle() if not args.title then return end has_rows = true root :tag('caption') :addClass('infobox-title') :addClass(args.titleclass) -- @deprecated next; target .infobox-<name> .infobox-title :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end has_rows = true root :tag('tr') :tag('th') :attr('colspan', '2') :addClass('infobox-above') :addClass(args.aboveclass) -- @deprecated next; target .infobox-<name> .infobox-above :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end has_rows = true root :tag('tr') :tag('td') :attr('colspan', '2') :addClass('infobox-below') :addClass(args.belowclass) -- @deprecated next; target .infobox-<name> .infobox-below :cssText(args.belowstyle) :wikitext(fixChildBoxes(args.below,'td')) end local function addSubheaderRow(subheaderArgs) if subheaderArgs.data and subheaderArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(subheaderArgs.rowclass) local dataCell = row:tag('td') dataCell :attr('colspan', '2') :addClass('infobox-subheader') :addClass(subheaderArgs.class) :cssText(subheaderArgs.datastyle) :cssText(subheaderArgs.rowcellstyle) :wikitext(fixChildBoxes(subheaderArgs.data, 'td')) else table.insert(empty_row_categories, subheaderArgs.data or '') end end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addSubheaderRow({ data = args['subheader' .. tostring(num)], -- @deprecated next; target .infobox-<name> .infobox-subheader datastyle = args.subheaderstyle, rowcellstyle = args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function addImageRow(imageArgs) if imageArgs.data and imageArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(imageArgs.rowclass) local dataCell = row:tag('td') dataCell :attr('colspan', '2') :addClass('infobox-image') :addClass(imageArgs.class) :cssText(imageArgs.datastyle) :wikitext(fixChildBoxes(imageArgs.data, 'td')) else table.insert(empty_row_categories, imageArgs.data or '') end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :addClass('infobox-caption') -- @deprecated next; target .infobox-<name> .infobox-caption :cssText(args.captionstyle) :wikitext(caption) end addImageRow({ data = tostring(data), -- @deprecated next; target .infobox-<name> .infobox-image datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end -- When autoheaders are turned on, preprocesses the rows local function preprocessRows() if not args.autoheaders then return end local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) local lastheader for k, num in ipairs(rownums) do if args['header' .. tostring(num)] then if lastheader then args['header' .. tostring(lastheader)] = nil end lastheader = num elseif args['data' .. tostring(num)] and args['data' .. tostring(num)]:gsub( category_in_empty_row_pattern, '' ):match('^%S') then local data = args['data' .. tostring(num)] if data:gsub(category_in_empty_row_pattern, ''):match('%S') then lastheader = nil end end end if lastheader then args['header' .. tostring(lastheader)] = nil end end -- Gets the union of the header and data argument numbers, -- and renders them all in order local function renderRows() local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], -- @deprecated next; target .infobox-<name> rowclass rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end has_rows = true root :tag('tr') :tag('td') :attr('colspan', '2') :addClass('infobox-navbar') :wikitext(require('Module:Navbar')._navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end -- Categories in otherwise empty rows are collected in empty_row_categories. -- This function adds them to the module output. It is not affected by -- args.decat because this module should not prevent module-external categories -- from rendering. local function renderEmptyRowCategories() for _, s in ipairs(empty_row_categories) do root:wikitext(s) end end -- Render tracking categories. args.decat == turns off tracking categories. local function renderTrackingCategories() if args.decat == 'yes' then return end if args.child == 'yes' then if args.title then root:wikitext( '[[Category:Pages which use embedded infobox templates with the title parameter]]' ) end elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') end end --[=[ Loads the templatestyles for the infobox. TODO: load base templatestyles here rather than in MediaWiki:Common.css We aren't doing it here yet because there are 4-5000 pages with 'raw' infobox tables. See [[Mediawiki_talk:Common.css/to_do#Infobox]] and/or come help :). When we do this we should clean up the inline CSS below too. Will have to do some bizarre conversion category like with sidebar. ]=] local function loadTemplateStyles() local frame = mw.getCurrentFrame() -- See function description -- local base_templatestyles = frame:extensionTag{ -- name = 'templatestyles', args = { src = cfg.i18n.templatestyles } -- } local templatestyles = '' if args['templatestyles'] then templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['templatestyles'] } } end local child_templatestyles = '' if args['child templatestyles'] then child_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['child templatestyles'] } } end local grandchild_templatestyles = '' if args['grandchild templatestyles'] then grandchild_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['grandchild templatestyles'] } } end return table.concat({ -- base_templatestyles, -- see function description templatestyles, child_templatestyles, grandchild_templatestyles }) end -- common functions between the child and non child cases local function structure_infobox_common() renderSubheaders() renderImages() preprocessRows() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderEmptyRowCategories() renderTrackingCategories() cleanInfobox() end -- Specify the overall layout of the infobox, with special settings if the -- infobox is used as a 'child' inside another infobox. local function _infobox() if args.child ~= 'yes' then root = mw.html.create('table') root :addClass(args.subbox == 'yes' and 'infobox-subbox' or 'infobox') :addClass(args.bodyclass) -- @deprecated next; target .infobox-<name> :cssText(args.bodystyle) renderTitle() renderAboveRow() structure_infobox_common() return loadTemplateStyles() .. root else root = mw.html.create() root :wikitext(args.title) structure_infobox_common() return root end end -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. local function preprocessSingleArg(argName) if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end -- Assign the parameters with the given prefixes to the args table, in order, in -- batches of the step size specified. This is to prevent references etc. from -- appearing in the wrong order. The prefixTable should be an array containing -- tables, each of which has two possible fields, a "prefix" string and a -- "depend" table. The function always parses parameters containing the "prefix" -- string, but only parses parameters in the "depend" table if the prefix -- parameter is present and non-blank. local function preprocessArgs(prefixTable, step) if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present -- and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then -- Do another loop if any arguments are found, even blank ones. moreArgumentsExist = true preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present -- and not blank, or we are processing "prefix1" and "prefix" is -- present and not blank, and if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end -- Parse the data parameters in the same order that the old {{infobox}} did, so -- that references etc. will display in the expected places. Parameters that -- depend on another parameter are only processed if that parameter is present, -- to avoid phantom references appearing in article reference lists. local function parseDataParameters() preprocessSingleArg('autoheaders') preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') -- different behaviour for italics if blank or absent args['italic title'] = origArgs['italic title'] preprocessSingleArg('decat') preprocessSingleArg('templatestyles') preprocessSingleArg('child templatestyles') preprocessSingleArg('grandchild templatestyles') end -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. function p.infobox(frame) if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end parseDataParameters() return _infobox() end -- For calling via #invoke within a template function p.infoboxTemplate(frame) origArgs = {} for k,v in pairs(frame.args) do origArgs[k] = mw.text.trim(v) end parseDataParameters() return _infobox() end return p nouue8knrrryohtsx43expp0hvz9k5a 803444 803443 2021-09-29T14:05:49Z en>Frietjes 0 (1) return templatestyles when child=yes, (2) move templatestyles and categories before </tr> to avoid creating empty cells (see thread on talk page) 803444 Scribunto text/plain local p = {} local args = {} local origArgs = {} local root local empty_row_categories = {} local category_in_empty_row_pattern = '%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]' local has_rows = false local function fixChildBoxes(sval, tt) local function notempty( s ) return s and s:match( '%S' ) end if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval -- start moving templatestyles and categories inside of table rows local slast = '' while slast ~= s do slast = s s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*%]%])', '%2%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(<templatestyles[^<>]*>%s*</templatestyles>)', '%2%1') end -- end moving templatestyles and categories inside of table rows s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end -- the next two lines add a newline at the end of lists for the PHP parser -- [[Special:Diff/849054481]] -- remove when [[:phab:T191516]] is fixed or OBE s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1') s = mw.ustring.gsub(s, '^(%{%|)', '\n%1') return s else return sval end end -- Cleans empty tables local function cleanInfobox() root = tostring(root) if has_rows == false then root = mw.ustring.gsub(root, '<table[^<>]*>%s*</table>', '') end end -- Returns the union of the values of two tables, as a sequence. local function union(t1, t2) local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local function getArgNums(prefix) local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. local function addRow(rowArgs) if rowArgs.header and rowArgs.header ~= '_BLANK_' then has_rows = true root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :tag('th') :attr('colspan', '2') :addClass('infobox-header') :addClass(rowArgs.class) :addClass(args.headerclass) -- @deprecated next; target .infobox-<name> .infobox-header :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) if rowArgs.data then root:wikitext( '[[Category:Pages which use infobox templates with ignored data cells]]' ) end elseif rowArgs.data and rowArgs.data:gsub( category_in_empty_row_pattern, '' ):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) if rowArgs.label then row :tag('th') :attr('scope', 'row') :addClass('infobox-label') -- @deprecated next; target .infobox-<name> .infobox-label :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') dataCell :attr('colspan', not rowArgs.label and '2' or nil) :addClass(not rowArgs.label and 'infobox-full-data' or 'infobox-data') :addClass(rowArgs.class) -- @deprecated next; target .infobox-<name> .infobox(-full)-data :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.data, 'td')) else table.insert(empty_row_categories, rowArgs.data or '') end end local function renderTitle() if not args.title then return end has_rows = true root :tag('caption') :addClass('infobox-title') :addClass(args.titleclass) -- @deprecated next; target .infobox-<name> .infobox-title :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end has_rows = true root :tag('tr') :tag('th') :attr('colspan', '2') :addClass('infobox-above') :addClass(args.aboveclass) -- @deprecated next; target .infobox-<name> .infobox-above :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end has_rows = true root :tag('tr') :tag('td') :attr('colspan', '2') :addClass('infobox-below') :addClass(args.belowclass) -- @deprecated next; target .infobox-<name> .infobox-below :cssText(args.belowstyle) :wikitext(fixChildBoxes(args.below,'td')) end local function addSubheaderRow(subheaderArgs) if subheaderArgs.data and subheaderArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(subheaderArgs.rowclass) local dataCell = row:tag('td') dataCell :attr('colspan', '2') :addClass('infobox-subheader') :addClass(subheaderArgs.class) :cssText(subheaderArgs.datastyle) :cssText(subheaderArgs.rowcellstyle) :wikitext(fixChildBoxes(subheaderArgs.data, 'td')) else table.insert(empty_row_categories, subheaderArgs.data or '') end end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addSubheaderRow({ data = args['subheader' .. tostring(num)], -- @deprecated next; target .infobox-<name> .infobox-subheader datastyle = args.subheaderstyle, rowcellstyle = args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function addImageRow(imageArgs) if imageArgs.data and imageArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(imageArgs.rowclass) local dataCell = row:tag('td') dataCell :attr('colspan', '2') :addClass('infobox-image') :addClass(imageArgs.class) :cssText(imageArgs.datastyle) :wikitext(fixChildBoxes(imageArgs.data, 'td')) else table.insert(empty_row_categories, imageArgs.data or '') end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :addClass('infobox-caption') -- @deprecated next; target .infobox-<name> .infobox-caption :cssText(args.captionstyle) :wikitext(caption) end addImageRow({ data = tostring(data), -- @deprecated next; target .infobox-<name> .infobox-image datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end -- When autoheaders are turned on, preprocesses the rows local function preprocessRows() if not args.autoheaders then return end local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) local lastheader for k, num in ipairs(rownums) do if args['header' .. tostring(num)] then if lastheader then args['header' .. tostring(lastheader)] = nil end lastheader = num elseif args['data' .. tostring(num)] and args['data' .. tostring(num)]:gsub( category_in_empty_row_pattern, '' ):match('^%S') then local data = args['data' .. tostring(num)] if data:gsub(category_in_empty_row_pattern, ''):match('%S') then lastheader = nil end end end if lastheader then args['header' .. tostring(lastheader)] = nil end end -- Gets the union of the header and data argument numbers, -- and renders them all in order local function renderRows() local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], -- @deprecated next; target .infobox-<name> rowclass rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end has_rows = true root :tag('tr') :tag('td') :attr('colspan', '2') :addClass('infobox-navbar') :wikitext(require('Module:Navbar')._navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end -- Categories in otherwise empty rows are collected in empty_row_categories. -- This function adds them to the module output. It is not affected by -- args.decat because this module should not prevent module-external categories -- from rendering. local function renderEmptyRowCategories() for _, s in ipairs(empty_row_categories) do root:wikitext(s) end end -- Render tracking categories. args.decat == turns off tracking categories. local function renderTrackingCategories() if args.decat == 'yes' then return end if args.child == 'yes' then if args.title then root:wikitext( '[[Category:Pages which use embedded infobox templates with the title parameter]]' ) end elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') end end --[=[ Loads the templatestyles for the infobox. TODO: load base templatestyles here rather than in MediaWiki:Common.css We aren't doing it here yet because there are 4-5000 pages with 'raw' infobox tables. See [[Mediawiki_talk:Common.css/to_do#Infobox]] and/or come help :). When we do this we should clean up the inline CSS below too. Will have to do some bizarre conversion category like with sidebar. ]=] local function loadTemplateStyles() local frame = mw.getCurrentFrame() -- See function description -- local base_templatestyles = frame:extensionTag{ -- name = 'templatestyles', args = { src = cfg.i18n.templatestyles } -- } local templatestyles = '' if args['templatestyles'] then templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['templatestyles'] } } end local child_templatestyles = '' if args['child templatestyles'] then child_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['child templatestyles'] } } end local grandchild_templatestyles = '' if args['grandchild templatestyles'] then grandchild_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['grandchild templatestyles'] } } end return table.concat({ -- base_templatestyles, -- see function description templatestyles, child_templatestyles, grandchild_templatestyles }) end -- common functions between the child and non child cases local function structure_infobox_common() renderSubheaders() renderImages() preprocessRows() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderEmptyRowCategories() renderTrackingCategories() cleanInfobox() end -- Specify the overall layout of the infobox, with special settings if the -- infobox is used as a 'child' inside another infobox. local function _infobox() if args.child ~= 'yes' then root = mw.html.create('table') root :addClass(args.subbox == 'yes' and 'infobox-subbox' or 'infobox') :addClass(args.bodyclass) -- @deprecated next; target .infobox-<name> :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end structure_infobox_common() return loadTemplateStyles() .. root end -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. local function preprocessSingleArg(argName) if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end -- Assign the parameters with the given prefixes to the args table, in order, in -- batches of the step size specified. This is to prevent references etc. from -- appearing in the wrong order. The prefixTable should be an array containing -- tables, each of which has two possible fields, a "prefix" string and a -- "depend" table. The function always parses parameters containing the "prefix" -- string, but only parses parameters in the "depend" table if the prefix -- parameter is present and non-blank. local function preprocessArgs(prefixTable, step) if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present -- and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then -- Do another loop if any arguments are found, even blank ones. moreArgumentsExist = true preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present -- and not blank, or we are processing "prefix1" and "prefix" is -- present and not blank, and if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end -- Parse the data parameters in the same order that the old {{infobox}} did, so -- that references etc. will display in the expected places. Parameters that -- depend on another parameter are only processed if that parameter is present, -- to avoid phantom references appearing in article reference lists. local function parseDataParameters() preprocessSingleArg('autoheaders') preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') -- different behaviour for italics if blank or absent args['italic title'] = origArgs['italic title'] preprocessSingleArg('decat') preprocessSingleArg('templatestyles') preprocessSingleArg('child templatestyles') preprocessSingleArg('grandchild templatestyles') end -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. function p.infobox(frame) if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end parseDataParameters() return _infobox() end -- For calling via #invoke within a template function p.infoboxTemplate(frame) origArgs = {} for k,v in pairs(frame.args) do origArgs[k] = mw.text.trim(v) end parseDataParameters() return _infobox() end return p kmblpziwagns5xikxi3vvxz4z9i1bwx 803445 803444 2021-10-07T14:44:35Z en>Izno 0 move subbox to templatestyles, still working on the other 4k. will remove from common.css Soon 803445 Scribunto text/plain local p = {} local args = {} local origArgs = {} local root local empty_row_categories = {} local category_in_empty_row_pattern = '%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]' local has_rows = false local function fixChildBoxes(sval, tt) local function notempty( s ) return s and s:match( '%S' ) end if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval -- start moving templatestyles and categories inside of table rows local slast = '' while slast ~= s do slast = s s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*%]%])', '%2%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(<templatestyles[^<>]*>%s*</templatestyles>)', '%2%1') end -- end moving templatestyles and categories inside of table rows s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end -- the next two lines add a newline at the end of lists for the PHP parser -- [[Special:Diff/849054481]] -- remove when [[:phab:T191516]] is fixed or OBE s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1') s = mw.ustring.gsub(s, '^(%{%|)', '\n%1') return s else return sval end end -- Cleans empty tables local function cleanInfobox() root = tostring(root) if has_rows == false then root = mw.ustring.gsub(root, '<table[^<>]*>%s*</table>', '') end end -- Returns the union of the values of two tables, as a sequence. local function union(t1, t2) local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local function getArgNums(prefix) local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. local function addRow(rowArgs) if rowArgs.header and rowArgs.header ~= '_BLANK_' then has_rows = true root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :tag('th') :attr('colspan', '2') :addClass('infobox-header') :addClass(rowArgs.class) :addClass(args.headerclass) -- @deprecated next; target .infobox-<name> .infobox-header :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) if rowArgs.data then root:wikitext( '[[Category:Pages which use infobox templates with ignored data cells]]' ) end elseif rowArgs.data and rowArgs.data:gsub( category_in_empty_row_pattern, '' ):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) if rowArgs.label then row :tag('th') :attr('scope', 'row') :addClass('infobox-label') -- @deprecated next; target .infobox-<name> .infobox-label :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') dataCell :attr('colspan', not rowArgs.label and '2' or nil) :addClass(not rowArgs.label and 'infobox-full-data' or 'infobox-data') :addClass(rowArgs.class) -- @deprecated next; target .infobox-<name> .infobox(-full)-data :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.data, 'td')) else table.insert(empty_row_categories, rowArgs.data or '') end end local function renderTitle() if not args.title then return end has_rows = true root :tag('caption') :addClass('infobox-title') :addClass(args.titleclass) -- @deprecated next; target .infobox-<name> .infobox-title :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end has_rows = true root :tag('tr') :tag('th') :attr('colspan', '2') :addClass('infobox-above') :addClass(args.aboveclass) -- @deprecated next; target .infobox-<name> .infobox-above :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end has_rows = true root :tag('tr') :tag('td') :attr('colspan', '2') :addClass('infobox-below') :addClass(args.belowclass) -- @deprecated next; target .infobox-<name> .infobox-below :cssText(args.belowstyle) :wikitext(fixChildBoxes(args.below,'td')) end local function addSubheaderRow(subheaderArgs) if subheaderArgs.data and subheaderArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(subheaderArgs.rowclass) local dataCell = row:tag('td') dataCell :attr('colspan', '2') :addClass('infobox-subheader') :addClass(subheaderArgs.class) :cssText(subheaderArgs.datastyle) :cssText(subheaderArgs.rowcellstyle) :wikitext(fixChildBoxes(subheaderArgs.data, 'td')) else table.insert(empty_row_categories, subheaderArgs.data or '') end end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addSubheaderRow({ data = args['subheader' .. tostring(num)], -- @deprecated next; target .infobox-<name> .infobox-subheader datastyle = args.subheaderstyle, rowcellstyle = args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function addImageRow(imageArgs) if imageArgs.data and imageArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(imageArgs.rowclass) local dataCell = row:tag('td') dataCell :attr('colspan', '2') :addClass('infobox-image') :addClass(imageArgs.class) :cssText(imageArgs.datastyle) :wikitext(fixChildBoxes(imageArgs.data, 'td')) else table.insert(empty_row_categories, imageArgs.data or '') end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :addClass('infobox-caption') -- @deprecated next; target .infobox-<name> .infobox-caption :cssText(args.captionstyle) :wikitext(caption) end addImageRow({ data = tostring(data), -- @deprecated next; target .infobox-<name> .infobox-image datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end -- When autoheaders are turned on, preprocesses the rows local function preprocessRows() if not args.autoheaders then return end local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) local lastheader for k, num in ipairs(rownums) do if args['header' .. tostring(num)] then if lastheader then args['header' .. tostring(lastheader)] = nil end lastheader = num elseif args['data' .. tostring(num)] and args['data' .. tostring(num)]:gsub( category_in_empty_row_pattern, '' ):match('^%S') then local data = args['data' .. tostring(num)] if data:gsub(category_in_empty_row_pattern, ''):match('%S') then lastheader = nil end end end if lastheader then args['header' .. tostring(lastheader)] = nil end end -- Gets the union of the header and data argument numbers, -- and renders them all in order local function renderRows() local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], -- @deprecated next; target .infobox-<name> rowclass rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end has_rows = true root :tag('tr') :tag('td') :attr('colspan', '2') :addClass('infobox-navbar') :wikitext(require('Module:Navbar')._navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end -- Categories in otherwise empty rows are collected in empty_row_categories. -- This function adds them to the module output. It is not affected by -- args.decat because this module should not prevent module-external categories -- from rendering. local function renderEmptyRowCategories() for _, s in ipairs(empty_row_categories) do root:wikitext(s) end end -- Render tracking categories. args.decat == turns off tracking categories. local function renderTrackingCategories() if args.decat == 'yes' then return end if args.child == 'yes' then if args.title then root:wikitext( '[[Category:Pages which use embedded infobox templates with the title parameter]]' ) end elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') end end --[=[ Loads the templatestyles for the infobox. TODO: FINISH loading base templatestyles here rather than in MediaWiki:Common.css. There are 4-5000 pages with 'raw' infobox tables. See [[Mediawiki_talk:Common.css/to_do#Infobox]] and/or come help :). When we do this we should clean up the inline CSS below too. Will have to do some bizarre conversion category like with sidebar. ]=] local function loadTemplateStyles() local frame = mw.getCurrentFrame() -- See function description local base_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = 'Module:Infobox/styles.css' } } local templatestyles = '' if args['templatestyles'] then templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['templatestyles'] } } end local child_templatestyles = '' if args['child templatestyles'] then child_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['child templatestyles'] } } end local grandchild_templatestyles = '' if args['grandchild templatestyles'] then grandchild_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['grandchild templatestyles'] } } end return table.concat({ base_templatestyles, -- see function description templatestyles, child_templatestyles, grandchild_templatestyles }) end -- common functions between the child and non child cases local function structure_infobox_common() renderSubheaders() renderImages() preprocessRows() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderEmptyRowCategories() renderTrackingCategories() cleanInfobox() end -- Specify the overall layout of the infobox, with special settings if the -- infobox is used as a 'child' inside another infobox. local function _infobox() if args.child ~= 'yes' then root = mw.html.create('table') root :addClass(args.subbox == 'yes' and 'infobox-subbox' or 'infobox') :addClass(args.bodyclass) -- @deprecated next; target .infobox-<name> :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end structure_infobox_common() return loadTemplateStyles() .. root end -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. local function preprocessSingleArg(argName) if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end -- Assign the parameters with the given prefixes to the args table, in order, in -- batches of the step size specified. This is to prevent references etc. from -- appearing in the wrong order. The prefixTable should be an array containing -- tables, each of which has two possible fields, a "prefix" string and a -- "depend" table. The function always parses parameters containing the "prefix" -- string, but only parses parameters in the "depend" table if the prefix -- parameter is present and non-blank. local function preprocessArgs(prefixTable, step) if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present -- and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then -- Do another loop if any arguments are found, even blank ones. moreArgumentsExist = true preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present -- and not blank, or we are processing "prefix1" and "prefix" is -- present and not blank, and if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end -- Parse the data parameters in the same order that the old {{infobox}} did, so -- that references etc. will display in the expected places. Parameters that -- depend on another parameter are only processed if that parameter is present, -- to avoid phantom references appearing in article reference lists. local function parseDataParameters() preprocessSingleArg('autoheaders') preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') -- different behaviour for italics if blank or absent args['italic title'] = origArgs['italic title'] preprocessSingleArg('decat') preprocessSingleArg('templatestyles') preprocessSingleArg('child templatestyles') preprocessSingleArg('grandchild templatestyles') end -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. function p.infobox(frame) if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end parseDataParameters() return _infobox() end -- For calling via #invoke within a template function p.infoboxTemplate(frame) origArgs = {} for k,v in pairs(frame.args) do origArgs[k] = mw.text.trim(v) end parseDataParameters() return _infobox() end return p cyfohv8e3uo359m1yfdtqd6lfpa0km6 803446 803445 2021-10-08T12:17:23Z en>Neveselbert 0 Reverted [[WP:AGF|good faith]] edits by [[Special:Contributions/Izno|Izno]] ([[User talk:Izno|talk]]): This edit appears to cause extraordinarily high spaces between lines on [[Template:Infobox officeholder]]. 803446 Scribunto text/plain local p = {} local args = {} local origArgs = {} local root local empty_row_categories = {} local category_in_empty_row_pattern = '%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]' local has_rows = false local function fixChildBoxes(sval, tt) local function notempty( s ) return s and s:match( '%S' ) end if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval -- start moving templatestyles and categories inside of table rows local slast = '' while slast ~= s do slast = s s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*%]%])', '%2%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(<templatestyles[^<>]*>%s*</templatestyles>)', '%2%1') end -- end moving templatestyles and categories inside of table rows s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end -- the next two lines add a newline at the end of lists for the PHP parser -- [[Special:Diff/849054481]] -- remove when [[:phab:T191516]] is fixed or OBE s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1') s = mw.ustring.gsub(s, '^(%{%|)', '\n%1') return s else return sval end end -- Cleans empty tables local function cleanInfobox() root = tostring(root) if has_rows == false then root = mw.ustring.gsub(root, '<table[^<>]*>%s*</table>', '') end end -- Returns the union of the values of two tables, as a sequence. local function union(t1, t2) local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local function getArgNums(prefix) local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. local function addRow(rowArgs) if rowArgs.header and rowArgs.header ~= '_BLANK_' then has_rows = true root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :tag('th') :attr('colspan', '2') :addClass('infobox-header') :addClass(rowArgs.class) :addClass(args.headerclass) -- @deprecated next; target .infobox-<name> .infobox-header :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) if rowArgs.data then root:wikitext( '[[Category:Pages which use infobox templates with ignored data cells]]' ) end elseif rowArgs.data and rowArgs.data:gsub( category_in_empty_row_pattern, '' ):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) if rowArgs.label then row :tag('th') :attr('scope', 'row') :addClass('infobox-label') -- @deprecated next; target .infobox-<name> .infobox-label :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') dataCell :attr('colspan', not rowArgs.label and '2' or nil) :addClass(not rowArgs.label and 'infobox-full-data' or 'infobox-data') :addClass(rowArgs.class) -- @deprecated next; target .infobox-<name> .infobox(-full)-data :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.data, 'td')) else table.insert(empty_row_categories, rowArgs.data or '') end end local function renderTitle() if not args.title then return end has_rows = true root :tag('caption') :addClass('infobox-title') :addClass(args.titleclass) -- @deprecated next; target .infobox-<name> .infobox-title :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end has_rows = true root :tag('tr') :tag('th') :attr('colspan', '2') :addClass('infobox-above') :addClass(args.aboveclass) -- @deprecated next; target .infobox-<name> .infobox-above :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end has_rows = true root :tag('tr') :tag('td') :attr('colspan', '2') :addClass('infobox-below') :addClass(args.belowclass) -- @deprecated next; target .infobox-<name> .infobox-below :cssText(args.belowstyle) :wikitext(fixChildBoxes(args.below,'td')) end local function addSubheaderRow(subheaderArgs) if subheaderArgs.data and subheaderArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(subheaderArgs.rowclass) local dataCell = row:tag('td') dataCell :attr('colspan', '2') :addClass('infobox-subheader') :addClass(subheaderArgs.class) :cssText(subheaderArgs.datastyle) :cssText(subheaderArgs.rowcellstyle) :wikitext(fixChildBoxes(subheaderArgs.data, 'td')) else table.insert(empty_row_categories, subheaderArgs.data or '') end end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addSubheaderRow({ data = args['subheader' .. tostring(num)], -- @deprecated next; target .infobox-<name> .infobox-subheader datastyle = args.subheaderstyle, rowcellstyle = args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function addImageRow(imageArgs) if imageArgs.data and imageArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(imageArgs.rowclass) local dataCell = row:tag('td') dataCell :attr('colspan', '2') :addClass('infobox-image') :addClass(imageArgs.class) :cssText(imageArgs.datastyle) :wikitext(fixChildBoxes(imageArgs.data, 'td')) else table.insert(empty_row_categories, imageArgs.data or '') end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :addClass('infobox-caption') -- @deprecated next; target .infobox-<name> .infobox-caption :cssText(args.captionstyle) :wikitext(caption) end addImageRow({ data = tostring(data), -- @deprecated next; target .infobox-<name> .infobox-image datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end -- When autoheaders are turned on, preprocesses the rows local function preprocessRows() if not args.autoheaders then return end local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) local lastheader for k, num in ipairs(rownums) do if args['header' .. tostring(num)] then if lastheader then args['header' .. tostring(lastheader)] = nil end lastheader = num elseif args['data' .. tostring(num)] and args['data' .. tostring(num)]:gsub( category_in_empty_row_pattern, '' ):match('^%S') then local data = args['data' .. tostring(num)] if data:gsub(category_in_empty_row_pattern, ''):match('%S') then lastheader = nil end end end if lastheader then args['header' .. tostring(lastheader)] = nil end end -- Gets the union of the header and data argument numbers, -- and renders them all in order local function renderRows() local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], -- @deprecated next; target .infobox-<name> rowclass rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end has_rows = true root :tag('tr') :tag('td') :attr('colspan', '2') :addClass('infobox-navbar') :wikitext(require('Module:Navbar')._navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end -- Categories in otherwise empty rows are collected in empty_row_categories. -- This function adds them to the module output. It is not affected by -- args.decat because this module should not prevent module-external categories -- from rendering. local function renderEmptyRowCategories() for _, s in ipairs(empty_row_categories) do root:wikitext(s) end end -- Render tracking categories. args.decat == turns off tracking categories. local function renderTrackingCategories() if args.decat == 'yes' then return end if args.child == 'yes' then if args.title then root:wikitext( '[[Category:Pages which use embedded infobox templates with the title parameter]]' ) end elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') end end --[=[ Loads the templatestyles for the infobox. TODO: load base templatestyles here rather than in MediaWiki:Common.css We aren't doing it here yet because there are 4-5000 pages with 'raw' infobox tables. See [[Mediawiki_talk:Common.css/to_do#Infobox]] and/or come help :). When we do this we should clean up the inline CSS below too. Will have to do some bizarre conversion category like with sidebar. ]=] local function loadTemplateStyles() local frame = mw.getCurrentFrame() -- See function description -- local base_templatestyles = frame:extensionTag{ -- name = 'templatestyles', args = { src = cfg.i18n.templatestyles } -- } local templatestyles = '' if args['templatestyles'] then templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['templatestyles'] } } end local child_templatestyles = '' if args['child templatestyles'] then child_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['child templatestyles'] } } end local grandchild_templatestyles = '' if args['grandchild templatestyles'] then grandchild_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['grandchild templatestyles'] } } end return table.concat({ -- base_templatestyles, -- see function description templatestyles, child_templatestyles, grandchild_templatestyles }) end -- common functions between the child and non child cases local function structure_infobox_common() renderSubheaders() renderImages() preprocessRows() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderEmptyRowCategories() renderTrackingCategories() cleanInfobox() end -- Specify the overall layout of the infobox, with special settings if the -- infobox is used as a 'child' inside another infobox. local function _infobox() if args.child ~= 'yes' then root = mw.html.create('table') root :addClass(args.subbox == 'yes' and 'infobox-subbox' or 'infobox') :addClass(args.bodyclass) -- @deprecated next; target .infobox-<name> :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end structure_infobox_common() return loadTemplateStyles() .. root end -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. local function preprocessSingleArg(argName) if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end -- Assign the parameters with the given prefixes to the args table, in order, in -- batches of the step size specified. This is to prevent references etc. from -- appearing in the wrong order. The prefixTable should be an array containing -- tables, each of which has two possible fields, a "prefix" string and a -- "depend" table. The function always parses parameters containing the "prefix" -- string, but only parses parameters in the "depend" table if the prefix -- parameter is present and non-blank. local function preprocessArgs(prefixTable, step) if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present -- and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then -- Do another loop if any arguments are found, even blank ones. moreArgumentsExist = true preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present -- and not blank, or we are processing "prefix1" and "prefix" is -- present and not blank, and if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end -- Parse the data parameters in the same order that the old {{infobox}} did, so -- that references etc. will display in the expected places. Parameters that -- depend on another parameter are only processed if that parameter is present, -- to avoid phantom references appearing in article reference lists. local function parseDataParameters() preprocessSingleArg('autoheaders') preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') -- different behaviour for italics if blank or absent args['italic title'] = origArgs['italic title'] preprocessSingleArg('decat') preprocessSingleArg('templatestyles') preprocessSingleArg('child templatestyles') preprocessSingleArg('grandchild templatestyles') end -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. function p.infobox(frame) if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end parseDataParameters() return _infobox() end -- For calling via #invoke within a template function p.infoboxTemplate(frame) origArgs = {} for k,v in pairs(frame.args) do origArgs[k] = mw.text.trim(v) end parseDataParameters() return _infobox() end return p kmblpziwagns5xikxi3vvxz4z9i1bwx 803447 803446 2021-10-10T17:00:17Z en>Plastikspork 0 Fix templatestyles pattern, this tag will be expanded into a strip-marker when processed here 803447 Scribunto text/plain local p = {} local args = {} local origArgs = {} local root local empty_row_categories = {} local category_in_empty_row_pattern = '%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]' local has_rows = false local function fixChildBoxes(sval, tt) local function notempty( s ) return s and s:match( '%S' ) end if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval -- start moving templatestyles and categories inside of table rows local slast = '' while slast ~= s do slast = s s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*%]%])', '%2%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(\127[^\127]*UNIQ%-%-templatestyles%-%x+%-QINU[^\127]*\127)', '%2%1') end -- end moving templatestyles and categories inside of table rows s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end -- the next two lines add a newline at the end of lists for the PHP parser -- [[Special:Diff/849054481]] -- remove when [[:phab:T191516]] is fixed or OBE s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1') s = mw.ustring.gsub(s, '^(%{%|)', '\n%1') return s else return sval end end -- Cleans empty tables local function cleanInfobox() root = tostring(root) if has_rows == false then root = mw.ustring.gsub(root, '<table[^<>]*>%s*</table>', '') end end -- Returns the union of the values of two tables, as a sequence. local function union(t1, t2) local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local function getArgNums(prefix) local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. local function addRow(rowArgs) if rowArgs.header and rowArgs.header ~= '_BLANK_' then has_rows = true root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :tag('th') :attr('colspan', '2') :addClass('infobox-header') :addClass(rowArgs.class) :addClass(args.headerclass) -- @deprecated next; target .infobox-<name> .infobox-header :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) if rowArgs.data then root:wikitext( '[[Category:Pages which use infobox templates with ignored data cells]]' ) end elseif rowArgs.data and rowArgs.data:gsub( category_in_empty_row_pattern, '' ):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) if rowArgs.label then row :tag('th') :attr('scope', 'row') :addClass('infobox-label') -- @deprecated next; target .infobox-<name> .infobox-label :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') dataCell :attr('colspan', not rowArgs.label and '2' or nil) :addClass(not rowArgs.label and 'infobox-full-data' or 'infobox-data') :addClass(rowArgs.class) -- @deprecated next; target .infobox-<name> .infobox(-full)-data :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.data, 'td')) else table.insert(empty_row_categories, rowArgs.data or '') end end local function renderTitle() if not args.title then return end has_rows = true root :tag('caption') :addClass('infobox-title') :addClass(args.titleclass) -- @deprecated next; target .infobox-<name> .infobox-title :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end has_rows = true root :tag('tr') :tag('th') :attr('colspan', '2') :addClass('infobox-above') :addClass(args.aboveclass) -- @deprecated next; target .infobox-<name> .infobox-above :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end has_rows = true root :tag('tr') :tag('td') :attr('colspan', '2') :addClass('infobox-below') :addClass(args.belowclass) -- @deprecated next; target .infobox-<name> .infobox-below :cssText(args.belowstyle) :wikitext(fixChildBoxes(args.below,'td')) end local function addSubheaderRow(subheaderArgs) if subheaderArgs.data and subheaderArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(subheaderArgs.rowclass) local dataCell = row:tag('td') dataCell :attr('colspan', '2') :addClass('infobox-subheader') :addClass(subheaderArgs.class) :cssText(subheaderArgs.datastyle) :cssText(subheaderArgs.rowcellstyle) :wikitext(fixChildBoxes(subheaderArgs.data, 'td')) else table.insert(empty_row_categories, subheaderArgs.data or '') end end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addSubheaderRow({ data = args['subheader' .. tostring(num)], -- @deprecated next; target .infobox-<name> .infobox-subheader datastyle = args.subheaderstyle, rowcellstyle = args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function addImageRow(imageArgs) if imageArgs.data and imageArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(imageArgs.rowclass) local dataCell = row:tag('td') dataCell :attr('colspan', '2') :addClass('infobox-image') :addClass(imageArgs.class) :cssText(imageArgs.datastyle) :wikitext(fixChildBoxes(imageArgs.data, 'td')) else table.insert(empty_row_categories, imageArgs.data or '') end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :addClass('infobox-caption') -- @deprecated next; target .infobox-<name> .infobox-caption :cssText(args.captionstyle) :wikitext(caption) end addImageRow({ data = tostring(data), -- @deprecated next; target .infobox-<name> .infobox-image datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end -- When autoheaders are turned on, preprocesses the rows local function preprocessRows() if not args.autoheaders then return end local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) local lastheader for k, num in ipairs(rownums) do if args['header' .. tostring(num)] then if lastheader then args['header' .. tostring(lastheader)] = nil end lastheader = num elseif args['data' .. tostring(num)] and args['data' .. tostring(num)]:gsub( category_in_empty_row_pattern, '' ):match('^%S') then local data = args['data' .. tostring(num)] if data:gsub(category_in_empty_row_pattern, ''):match('%S') then lastheader = nil end end end if lastheader then args['header' .. tostring(lastheader)] = nil end end -- Gets the union of the header and data argument numbers, -- and renders them all in order local function renderRows() local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], -- @deprecated next; target .infobox-<name> rowclass rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end has_rows = true root :tag('tr') :tag('td') :attr('colspan', '2') :addClass('infobox-navbar') :wikitext(require('Module:Navbar')._navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end -- Categories in otherwise empty rows are collected in empty_row_categories. -- This function adds them to the module output. It is not affected by -- args.decat because this module should not prevent module-external categories -- from rendering. local function renderEmptyRowCategories() for _, s in ipairs(empty_row_categories) do root:wikitext(s) end end -- Render tracking categories. args.decat == turns off tracking categories. local function renderTrackingCategories() if args.decat == 'yes' then return end if args.child == 'yes' then if args.title then root:wikitext( '[[Category:Pages which use embedded infobox templates with the title parameter]]' ) end elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') end end --[=[ Loads the templatestyles for the infobox. TODO: load base templatestyles here rather than in MediaWiki:Common.css We aren't doing it here yet because there are 4-5000 pages with 'raw' infobox tables. See [[Mediawiki_talk:Common.css/to_do#Infobox]] and/or come help :). When we do this we should clean up the inline CSS below too. Will have to do some bizarre conversion category like with sidebar. ]=] local function loadTemplateStyles() local frame = mw.getCurrentFrame() -- See function description -- local base_templatestyles = frame:extensionTag{ -- name = 'templatestyles', args = { src = cfg.i18n.templatestyles } -- } local templatestyles = '' if args['templatestyles'] then templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['templatestyles'] } } end local child_templatestyles = '' if args['child templatestyles'] then child_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['child templatestyles'] } } end local grandchild_templatestyles = '' if args['grandchild templatestyles'] then grandchild_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['grandchild templatestyles'] } } end return table.concat({ -- base_templatestyles, -- see function description templatestyles, child_templatestyles, grandchild_templatestyles }) end -- common functions between the child and non child cases local function structure_infobox_common() renderSubheaders() renderImages() preprocessRows() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderEmptyRowCategories() renderTrackingCategories() cleanInfobox() end -- Specify the overall layout of the infobox, with special settings if the -- infobox is used as a 'child' inside another infobox. local function _infobox() if args.child ~= 'yes' then root = mw.html.create('table') root :addClass(args.subbox == 'yes' and 'infobox-subbox' or 'infobox') :addClass(args.bodyclass) -- @deprecated next; target .infobox-<name> :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end structure_infobox_common() return loadTemplateStyles() .. root end -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. local function preprocessSingleArg(argName) if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end -- Assign the parameters with the given prefixes to the args table, in order, in -- batches of the step size specified. This is to prevent references etc. from -- appearing in the wrong order. The prefixTable should be an array containing -- tables, each of which has two possible fields, a "prefix" string and a -- "depend" table. The function always parses parameters containing the "prefix" -- string, but only parses parameters in the "depend" table if the prefix -- parameter is present and non-blank. local function preprocessArgs(prefixTable, step) if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present -- and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then -- Do another loop if any arguments are found, even blank ones. moreArgumentsExist = true preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present -- and not blank, or we are processing "prefix1" and "prefix" is -- present and not blank, and if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end -- Parse the data parameters in the same order that the old {{infobox}} did, so -- that references etc. will display in the expected places. Parameters that -- depend on another parameter are only processed if that parameter is present, -- to avoid phantom references appearing in article reference lists. local function parseDataParameters() preprocessSingleArg('autoheaders') preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') -- different behaviour for italics if blank or absent args['italic title'] = origArgs['italic title'] preprocessSingleArg('decat') preprocessSingleArg('templatestyles') preprocessSingleArg('child templatestyles') preprocessSingleArg('grandchild templatestyles') end -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. function p.infobox(frame) if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end parseDataParameters() return _infobox() end -- For calling via #invoke within a template function p.infoboxTemplate(frame) origArgs = {} for k,v in pairs(frame.args) do origArgs[k] = mw.text.trim(v) end parseDataParameters() return _infobox() end return p 7vhoomboweqig59rxhawfsxxzqb6nem 803448 803447 2021-10-11T22:21:33Z en>Izno 0 uno mas, based on plastikspork's work 803448 Scribunto text/plain local p = {} local args = {} local origArgs = {} local root local empty_row_categories = {} local category_in_empty_row_pattern = '%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]' local has_rows = false local function fixChildBoxes(sval, tt) local function notempty( s ) return s and s:match( '%S' ) end if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval -- start moving templatestyles and categories inside of table rows local slast = '' while slast ~= s do slast = s s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*%]%])', '%2%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(\127[^\127]*UNIQ%-%-templatestyles%-%x+%-QINU[^\127]*\127)', '%2%1') end -- end moving templatestyles and categories inside of table rows s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end -- the next two lines add a newline at the end of lists for the PHP parser -- [[Special:Diff/849054481]] -- remove when [[:phab:T191516]] is fixed or OBE s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1') s = mw.ustring.gsub(s, '^(%{%|)', '\n%1') return s else return sval end end -- Cleans empty tables local function cleanInfobox() root = tostring(root) if has_rows == false then root = mw.ustring.gsub(root, '<table[^<>]*>%s*</table>', '') end end -- Returns the union of the values of two tables, as a sequence. local function union(t1, t2) local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local function getArgNums(prefix) local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. local function addRow(rowArgs) if rowArgs.header and rowArgs.header ~= '_BLANK_' then has_rows = true root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :tag('th') :attr('colspan', '2') :addClass('infobox-header') :addClass(rowArgs.class) :addClass(args.headerclass) -- @deprecated next; target .infobox-<name> .infobox-header :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) if rowArgs.data then root:wikitext( '[[Category:Pages which use infobox templates with ignored data cells]]' ) end elseif rowArgs.data and rowArgs.data:gsub( category_in_empty_row_pattern, '' ):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) if rowArgs.label then row :tag('th') :attr('scope', 'row') :addClass('infobox-label') -- @deprecated next; target .infobox-<name> .infobox-label :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') dataCell :attr('colspan', not rowArgs.label and '2' or nil) :addClass(not rowArgs.label and 'infobox-full-data' or 'infobox-data') :addClass(rowArgs.class) -- @deprecated next; target .infobox-<name> .infobox(-full)-data :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.data, 'td')) else table.insert(empty_row_categories, rowArgs.data or '') end end local function renderTitle() if not args.title then return end has_rows = true root :tag('caption') :addClass('infobox-title') :addClass(args.titleclass) -- @deprecated next; target .infobox-<name> .infobox-title :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end has_rows = true root :tag('tr') :tag('th') :attr('colspan', '2') :addClass('infobox-above') :addClass(args.aboveclass) -- @deprecated next; target .infobox-<name> .infobox-above :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end has_rows = true root :tag('tr') :tag('td') :attr('colspan', '2') :addClass('infobox-below') :addClass(args.belowclass) -- @deprecated next; target .infobox-<name> .infobox-below :cssText(args.belowstyle) :wikitext(fixChildBoxes(args.below,'td')) end local function addSubheaderRow(subheaderArgs) if subheaderArgs.data and subheaderArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(subheaderArgs.rowclass) local dataCell = row:tag('td') dataCell :attr('colspan', '2') :addClass('infobox-subheader') :addClass(subheaderArgs.class) :cssText(subheaderArgs.datastyle) :cssText(subheaderArgs.rowcellstyle) :wikitext(fixChildBoxes(subheaderArgs.data, 'td')) else table.insert(empty_row_categories, subheaderArgs.data or '') end end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addSubheaderRow({ data = args['subheader' .. tostring(num)], -- @deprecated next; target .infobox-<name> .infobox-subheader datastyle = args.subheaderstyle, rowcellstyle = args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function addImageRow(imageArgs) if imageArgs.data and imageArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(imageArgs.rowclass) local dataCell = row:tag('td') dataCell :attr('colspan', '2') :addClass('infobox-image') :addClass(imageArgs.class) :cssText(imageArgs.datastyle) :wikitext(fixChildBoxes(imageArgs.data, 'td')) else table.insert(empty_row_categories, imageArgs.data or '') end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :addClass('infobox-caption') -- @deprecated next; target .infobox-<name> .infobox-caption :cssText(args.captionstyle) :wikitext(caption) end addImageRow({ data = tostring(data), -- @deprecated next; target .infobox-<name> .infobox-image datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end -- When autoheaders are turned on, preprocesses the rows local function preprocessRows() if not args.autoheaders then return end local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) local lastheader for k, num in ipairs(rownums) do if args['header' .. tostring(num)] then if lastheader then args['header' .. tostring(lastheader)] = nil end lastheader = num elseif args['data' .. tostring(num)] and args['data' .. tostring(num)]:gsub( category_in_empty_row_pattern, '' ):match('^%S') then local data = args['data' .. tostring(num)] if data:gsub(category_in_empty_row_pattern, ''):match('%S') then lastheader = nil end end end if lastheader then args['header' .. tostring(lastheader)] = nil end end -- Gets the union of the header and data argument numbers, -- and renders them all in order local function renderRows() local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], -- @deprecated next; target .infobox-<name> rowclass rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end has_rows = true root :tag('tr') :tag('td') :attr('colspan', '2') :addClass('infobox-navbar') :wikitext(require('Module:Navbar')._navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end -- Categories in otherwise empty rows are collected in empty_row_categories. -- This function adds them to the module output. It is not affected by -- args.decat because this module should not prevent module-external categories -- from rendering. local function renderEmptyRowCategories() for _, s in ipairs(empty_row_categories) do root:wikitext(s) end end -- Render tracking categories. args.decat == turns off tracking categories. local function renderTrackingCategories() if args.decat == 'yes' then return end if args.child == 'yes' then if args.title then root:wikitext( '[[Category:Pages which use embedded infobox templates with the title parameter]]' ) end elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') end end --[=[ Loads the templatestyles for the infobox. TODO: FINISH loading base templatestyles here rather than in MediaWiki:Common.css. There are 4-5000 pages with 'raw' infobox tables. See [[Mediawiki_talk:Common.css/to_do#Infobox]] and/or come help :). When we do this we should clean up the inline CSS below too. Will have to do some bizarre conversion category like with sidebar. ]=] local function loadTemplateStyles() local frame = mw.getCurrentFrame() -- See function description local base_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = 'Module:Infobox/styles.css' } } local templatestyles = '' if args['templatestyles'] then templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['templatestyles'] } } end local child_templatestyles = '' if args['child templatestyles'] then child_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['child templatestyles'] } } end local grandchild_templatestyles = '' if args['grandchild templatestyles'] then grandchild_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['grandchild templatestyles'] } } end return table.concat({ base_templatestyles, -- see function description templatestyles, child_templatestyles, grandchild_templatestyles }) end -- common functions between the child and non child cases local function structure_infobox_common() renderSubheaders() renderImages() preprocessRows() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderEmptyRowCategories() renderTrackingCategories() cleanInfobox() end -- Specify the overall layout of the infobox, with special settings if the -- infobox is used as a 'child' inside another infobox. local function _infobox() if args.child ~= 'yes' then root = mw.html.create('table') root :addClass(args.subbox == 'yes' and 'infobox-subbox' or 'infobox') :addClass(args.bodyclass) -- @deprecated next; target .infobox-<name> :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end structure_infobox_common() return loadTemplateStyles() .. root end -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. local function preprocessSingleArg(argName) if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end -- Assign the parameters with the given prefixes to the args table, in order, in -- batches of the step size specified. This is to prevent references etc. from -- appearing in the wrong order. The prefixTable should be an array containing -- tables, each of which has two possible fields, a "prefix" string and a -- "depend" table. The function always parses parameters containing the "prefix" -- string, but only parses parameters in the "depend" table if the prefix -- parameter is present and non-blank. local function preprocessArgs(prefixTable, step) if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present -- and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then -- Do another loop if any arguments are found, even blank ones. moreArgumentsExist = true preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present -- and not blank, or we are processing "prefix1" and "prefix" is -- present and not blank, and if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end -- Parse the data parameters in the same order that the old {{infobox}} did, so -- that references etc. will display in the expected places. Parameters that -- depend on another parameter are only processed if that parameter is present, -- to avoid phantom references appearing in article reference lists. local function parseDataParameters() preprocessSingleArg('autoheaders') preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') -- different behaviour for italics if blank or absent args['italic title'] = origArgs['italic title'] preprocessSingleArg('decat') preprocessSingleArg('templatestyles') preprocessSingleArg('child templatestyles') preprocessSingleArg('grandchild templatestyles') end -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. function p.infobox(frame) if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end parseDataParameters() return _infobox() end -- For calling via #invoke within a template function p.infoboxTemplate(frame) origArgs = {} for k,v in pairs(frame.args) do origArgs[k] = mw.text.trim(v) end parseDataParameters() return _infobox() end return p j79y42zpoooozmpk71k415bkisxo26u 803449 803448 2021-10-11T22:22:54Z en>Izno 0 ocd triggered 803449 Scribunto text/plain local p = {} local args = {} local origArgs = {} local root local empty_row_categories = {} local category_in_empty_row_pattern = '%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]' local has_rows = false local function fixChildBoxes(sval, tt) local function notempty( s ) return s and s:match( '%S' ) end if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval -- start moving templatestyles and categories inside of table rows local slast = '' while slast ~= s do slast = s s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*%]%])', '%2%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(\127[^\127]*UNIQ%-%-templatestyles%-%x+%-QINU[^\127]*\127)', '%2%1') end -- end moving templatestyles and categories inside of table rows s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end -- the next two lines add a newline at the end of lists for the PHP parser -- [[Special:Diff/849054481]] -- remove when [[:phab:T191516]] is fixed or OBE s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1') s = mw.ustring.gsub(s, '^(%{%|)', '\n%1') return s else return sval end end -- Cleans empty tables local function cleanInfobox() root = tostring(root) if has_rows == false then root = mw.ustring.gsub(root, '<table[^<>]*>%s*</table>', '') end end -- Returns the union of the values of two tables, as a sequence. local function union(t1, t2) local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local function getArgNums(prefix) local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. local function addRow(rowArgs) if rowArgs.header and rowArgs.header ~= '_BLANK_' then has_rows = true root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :tag('th') :attr('colspan', '2') :addClass('infobox-header') :addClass(rowArgs.class) :addClass(args.headerclass) -- @deprecated next; target .infobox-<name> .infobox-header :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) if rowArgs.data then root:wikitext( '[[Category:Pages which use infobox templates with ignored data cells]]' ) end elseif rowArgs.data and rowArgs.data:gsub( category_in_empty_row_pattern, '' ):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) if rowArgs.label then row :tag('th') :attr('scope', 'row') :addClass('infobox-label') -- @deprecated next; target .infobox-<name> .infobox-label :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') dataCell :attr('colspan', not rowArgs.label and '2' or nil) :addClass(not rowArgs.label and 'infobox-full-data' or 'infobox-data') :addClass(rowArgs.class) -- @deprecated next; target .infobox-<name> .infobox(-full)-data :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.data, 'td')) else table.insert(empty_row_categories, rowArgs.data or '') end end local function renderTitle() if not args.title then return end has_rows = true root :tag('caption') :addClass('infobox-title') :addClass(args.titleclass) -- @deprecated next; target .infobox-<name> .infobox-title :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end has_rows = true root :tag('tr') :tag('th') :attr('colspan', '2') :addClass('infobox-above') :addClass(args.aboveclass) -- @deprecated next; target .infobox-<name> .infobox-above :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end has_rows = true root :tag('tr') :tag('td') :attr('colspan', '2') :addClass('infobox-below') :addClass(args.belowclass) -- @deprecated next; target .infobox-<name> .infobox-below :cssText(args.belowstyle) :wikitext(fixChildBoxes(args.below,'td')) end local function addSubheaderRow(subheaderArgs) if subheaderArgs.data and subheaderArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(subheaderArgs.rowclass) local dataCell = row:tag('td') dataCell :attr('colspan', '2') :addClass('infobox-subheader') :addClass(subheaderArgs.class) :cssText(subheaderArgs.datastyle) :cssText(subheaderArgs.rowcellstyle) :wikitext(fixChildBoxes(subheaderArgs.data, 'td')) else table.insert(empty_row_categories, subheaderArgs.data or '') end end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addSubheaderRow({ data = args['subheader' .. tostring(num)], -- @deprecated next; target .infobox-<name> .infobox-subheader datastyle = args.subheaderstyle, rowcellstyle = args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function addImageRow(imageArgs) if imageArgs.data and imageArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(imageArgs.rowclass) local dataCell = row:tag('td') dataCell :attr('colspan', '2') :addClass('infobox-image') :addClass(imageArgs.class) :cssText(imageArgs.datastyle) :wikitext(fixChildBoxes(imageArgs.data, 'td')) else table.insert(empty_row_categories, imageArgs.data or '') end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :addClass('infobox-caption') -- @deprecated next; target .infobox-<name> .infobox-caption :cssText(args.captionstyle) :wikitext(caption) end addImageRow({ data = tostring(data), -- @deprecated next; target .infobox-<name> .infobox-image datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end -- When autoheaders are turned on, preprocesses the rows local function preprocessRows() if not args.autoheaders then return end local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) local lastheader for k, num in ipairs(rownums) do if args['header' .. tostring(num)] then if lastheader then args['header' .. tostring(lastheader)] = nil end lastheader = num elseif args['data' .. tostring(num)] and args['data' .. tostring(num)]:gsub( category_in_empty_row_pattern, '' ):match('^%S') then local data = args['data' .. tostring(num)] if data:gsub(category_in_empty_row_pattern, ''):match('%S') then lastheader = nil end end end if lastheader then args['header' .. tostring(lastheader)] = nil end end -- Gets the union of the header and data argument numbers, -- and renders them all in order local function renderRows() local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], -- @deprecated next; target .infobox-<name> rowclass rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end has_rows = true root :tag('tr') :tag('td') :attr('colspan', '2') :addClass('infobox-navbar') :wikitext(require('Module:Navbar')._navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end -- Categories in otherwise empty rows are collected in empty_row_categories. -- This function adds them to the module output. It is not affected by -- args.decat because this module should not prevent module-external categories -- from rendering. local function renderEmptyRowCategories() for _, s in ipairs(empty_row_categories) do root:wikitext(s) end end -- Render tracking categories. args.decat == turns off tracking categories. local function renderTrackingCategories() if args.decat == 'yes' then return end if args.child == 'yes' then if args.title then root:wikitext( '[[Category:Pages which use embedded infobox templates with the title parameter]]' ) end elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles which use infobox templates with no data rows]]') end end --[=[ Loads the templatestyles for the infobox. TODO: FINISH loading base templatestyles here rather than in MediaWiki:Common.css. There are 4-5000 pages with 'raw' infobox tables. See [[Mediawiki_talk:Common.css/to_do#Infobox]] and/or come help :). When we do this we should clean up the inline CSS below too. Will have to do some bizarre conversion category like with sidebar. ]=] local function loadTemplateStyles() local frame = mw.getCurrentFrame() -- See function description local base_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = 'Module:Infobox/styles.css' } } local templatestyles = '' if args['templatestyles'] then templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['templatestyles'] } } end local child_templatestyles = '' if args['child templatestyles'] then child_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['child templatestyles'] } } end local grandchild_templatestyles = '' if args['grandchild templatestyles'] then grandchild_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['grandchild templatestyles'] } } end return table.concat({ base_templatestyles, -- see function description templatestyles, child_templatestyles, grandchild_templatestyles }) end -- common functions between the child and non child cases local function structure_infobox_common() renderSubheaders() renderImages() preprocessRows() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderEmptyRowCategories() renderTrackingCategories() cleanInfobox() end -- Specify the overall layout of the infobox, with special settings if the -- infobox is used as a 'child' inside another infobox. local function _infobox() if args.child ~= 'yes' then root = mw.html.create('table') root :addClass(args.subbox == 'yes' and 'infobox-subbox' or 'infobox') :addClass(args.bodyclass) -- @deprecated next; target .infobox-<name> :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end structure_infobox_common() return loadTemplateStyles() .. root end -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. local function preprocessSingleArg(argName) if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end -- Assign the parameters with the given prefixes to the args table, in order, in -- batches of the step size specified. This is to prevent references etc. from -- appearing in the wrong order. The prefixTable should be an array containing -- tables, each of which has two possible fields, a "prefix" string and a -- "depend" table. The function always parses parameters containing the "prefix" -- string, but only parses parameters in the "depend" table if the prefix -- parameter is present and non-blank. local function preprocessArgs(prefixTable, step) if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present -- and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then -- Do another loop if any arguments are found, even blank ones. moreArgumentsExist = true preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present -- and not blank, or we are processing "prefix1" and "prefix" is -- present and not blank, and if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end -- Parse the data parameters in the same order that the old {{infobox}} did, so -- that references etc. will display in the expected places. Parameters that -- depend on another parameter are only processed if that parameter is present, -- to avoid phantom references appearing in article reference lists. local function parseDataParameters() preprocessSingleArg('autoheaders') preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') -- different behaviour for italics if blank or absent args['italic title'] = origArgs['italic title'] preprocessSingleArg('decat') preprocessSingleArg('templatestyles') preprocessSingleArg('child templatestyles') preprocessSingleArg('grandchild templatestyles') end -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. function p.infobox(frame) if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end parseDataParameters() return _infobox() end -- For calling via #invoke within a template function p.infoboxTemplate(frame) origArgs = {} for k,v in pairs(frame.args) do origArgs[k] = mw.text.trim(v) end parseDataParameters() return _infobox() end return p mub4z1wjimlb7sij0tmufeqdgcb806l 803450 803449 2021-11-16T10:21:29Z en>Fayenatic london 0 update category link 803450 Scribunto text/plain local p = {} local args = {} local origArgs = {} local root local empty_row_categories = {} local category_in_empty_row_pattern = '%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]' local has_rows = false local function fixChildBoxes(sval, tt) local function notempty( s ) return s and s:match( '%S' ) end if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval -- start moving templatestyles and categories inside of table rows local slast = '' while slast ~= s do slast = s s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*%]%])', '%2%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(\127[^\127]*UNIQ%-%-templatestyles%-%x+%-QINU[^\127]*\127)', '%2%1') end -- end moving templatestyles and categories inside of table rows s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end -- the next two lines add a newline at the end of lists for the PHP parser -- [[Special:Diff/849054481]] -- remove when [[:phab:T191516]] is fixed or OBE s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1') s = mw.ustring.gsub(s, '^(%{%|)', '\n%1') return s else return sval end end -- Cleans empty tables local function cleanInfobox() root = tostring(root) if has_rows == false then root = mw.ustring.gsub(root, '<table[^<>]*>%s*</table>', '') end end -- Returns the union of the values of two tables, as a sequence. local function union(t1, t2) local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local function getArgNums(prefix) local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. local function addRow(rowArgs) if rowArgs.header and rowArgs.header ~= '_BLANK_' then has_rows = true root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :tag('th') :attr('colspan', '2') :addClass('infobox-header') :addClass(rowArgs.class) :addClass(args.headerclass) -- @deprecated next; target .infobox-<name> .infobox-header :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) if rowArgs.data then root:wikitext( '[[Category:Pages which use infobox templates with ignored data cells]]' ) end elseif rowArgs.data and rowArgs.data:gsub( category_in_empty_row_pattern, '' ):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) if rowArgs.label then row :tag('th') :attr('scope', 'row') :addClass('infobox-label') -- @deprecated next; target .infobox-<name> .infobox-label :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') dataCell :attr('colspan', not rowArgs.label and '2' or nil) :addClass(not rowArgs.label and 'infobox-full-data' or 'infobox-data') :addClass(rowArgs.class) -- @deprecated next; target .infobox-<name> .infobox(-full)-data :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.data, 'td')) else table.insert(empty_row_categories, rowArgs.data or '') end end local function renderTitle() if not args.title then return end has_rows = true root :tag('caption') :addClass('infobox-title') :addClass(args.titleclass) -- @deprecated next; target .infobox-<name> .infobox-title :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end has_rows = true root :tag('tr') :tag('th') :attr('colspan', '2') :addClass('infobox-above') :addClass(args.aboveclass) -- @deprecated next; target .infobox-<name> .infobox-above :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end has_rows = true root :tag('tr') :tag('td') :attr('colspan', '2') :addClass('infobox-below') :addClass(args.belowclass) -- @deprecated next; target .infobox-<name> .infobox-below :cssText(args.belowstyle) :wikitext(fixChildBoxes(args.below,'td')) end local function addSubheaderRow(subheaderArgs) if subheaderArgs.data and subheaderArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(subheaderArgs.rowclass) local dataCell = row:tag('td') dataCell :attr('colspan', '2') :addClass('infobox-subheader') :addClass(subheaderArgs.class) :cssText(subheaderArgs.datastyle) :cssText(subheaderArgs.rowcellstyle) :wikitext(fixChildBoxes(subheaderArgs.data, 'td')) else table.insert(empty_row_categories, subheaderArgs.data or '') end end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addSubheaderRow({ data = args['subheader' .. tostring(num)], -- @deprecated next; target .infobox-<name> .infobox-subheader datastyle = args.subheaderstyle, rowcellstyle = args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function addImageRow(imageArgs) if imageArgs.data and imageArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(imageArgs.rowclass) local dataCell = row:tag('td') dataCell :attr('colspan', '2') :addClass('infobox-image') :addClass(imageArgs.class) :cssText(imageArgs.datastyle) :wikitext(fixChildBoxes(imageArgs.data, 'td')) else table.insert(empty_row_categories, imageArgs.data or '') end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :addClass('infobox-caption') -- @deprecated next; target .infobox-<name> .infobox-caption :cssText(args.captionstyle) :wikitext(caption) end addImageRow({ data = tostring(data), -- @deprecated next; target .infobox-<name> .infobox-image datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end -- When autoheaders are turned on, preprocesses the rows local function preprocessRows() if not args.autoheaders then return end local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) local lastheader for k, num in ipairs(rownums) do if args['header' .. tostring(num)] then if lastheader then args['header' .. tostring(lastheader)] = nil end lastheader = num elseif args['data' .. tostring(num)] and args['data' .. tostring(num)]:gsub( category_in_empty_row_pattern, '' ):match('^%S') then local data = args['data' .. tostring(num)] if data:gsub(category_in_empty_row_pattern, ''):match('%S') then lastheader = nil end end end if lastheader then args['header' .. tostring(lastheader)] = nil end end -- Gets the union of the header and data argument numbers, -- and renders them all in order local function renderRows() local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], -- @deprecated next; target .infobox-<name> rowclass rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end has_rows = true root :tag('tr') :tag('td') :attr('colspan', '2') :addClass('infobox-navbar') :wikitext(require('Module:Navbar')._navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end -- Categories in otherwise empty rows are collected in empty_row_categories. -- This function adds them to the module output. It is not affected by -- args.decat because this module should not prevent module-external categories -- from rendering. local function renderEmptyRowCategories() for _, s in ipairs(empty_row_categories) do root:wikitext(s) end end -- Render tracking categories. args.decat == turns off tracking categories. local function renderTrackingCategories() if args.decat == 'yes' then return end if args.child == 'yes' then if args.title then root:wikitext( '[[Category:Pages which use embedded infobox templates with the title parameter]]' ) end elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles using infobox templates with no data rows]]') end end --[=[ Loads the templatestyles for the infobox. TODO: FINISH loading base templatestyles here rather than in MediaWiki:Common.css. There are 4-5000 pages with 'raw' infobox tables. See [[Mediawiki_talk:Common.css/to_do#Infobox]] and/or come help :). When we do this we should clean up the inline CSS below too. Will have to do some bizarre conversion category like with sidebar. ]=] local function loadTemplateStyles() local frame = mw.getCurrentFrame() -- See function description local base_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = 'Module:Infobox/styles.css' } } local templatestyles = '' if args['templatestyles'] then templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['templatestyles'] } } end local child_templatestyles = '' if args['child templatestyles'] then child_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['child templatestyles'] } } end local grandchild_templatestyles = '' if args['grandchild templatestyles'] then grandchild_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['grandchild templatestyles'] } } end return table.concat({ base_templatestyles, -- see function description templatestyles, child_templatestyles, grandchild_templatestyles }) end -- common functions between the child and non child cases local function structure_infobox_common() renderSubheaders() renderImages() preprocessRows() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderEmptyRowCategories() renderTrackingCategories() cleanInfobox() end -- Specify the overall layout of the infobox, with special settings if the -- infobox is used as a 'child' inside another infobox. local function _infobox() if args.child ~= 'yes' then root = mw.html.create('table') root :addClass(args.subbox == 'yes' and 'infobox-subbox' or 'infobox') :addClass(args.bodyclass) -- @deprecated next; target .infobox-<name> :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end structure_infobox_common() return loadTemplateStyles() .. root end -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. local function preprocessSingleArg(argName) if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end -- Assign the parameters with the given prefixes to the args table, in order, in -- batches of the step size specified. This is to prevent references etc. from -- appearing in the wrong order. The prefixTable should be an array containing -- tables, each of which has two possible fields, a "prefix" string and a -- "depend" table. The function always parses parameters containing the "prefix" -- string, but only parses parameters in the "depend" table if the prefix -- parameter is present and non-blank. local function preprocessArgs(prefixTable, step) if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present -- and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then -- Do another loop if any arguments are found, even blank ones. moreArgumentsExist = true preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present -- and not blank, or we are processing "prefix1" and "prefix" is -- present and not blank, and if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end -- Parse the data parameters in the same order that the old {{infobox}} did, so -- that references etc. will display in the expected places. Parameters that -- depend on another parameter are only processed if that parameter is present, -- to avoid phantom references appearing in article reference lists. local function parseDataParameters() preprocessSingleArg('autoheaders') preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') -- different behaviour for italics if blank or absent args['italic title'] = origArgs['italic title'] preprocessSingleArg('decat') preprocessSingleArg('templatestyles') preprocessSingleArg('child templatestyles') preprocessSingleArg('grandchild templatestyles') end -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. function p.infobox(frame) if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end parseDataParameters() return _infobox() end -- For calling via #invoke within a template function p.infoboxTemplate(frame) origArgs = {} for k,v in pairs(frame.args) do origArgs[k] = mw.text.trim(v) end parseDataParameters() return _infobox() end return p 8h8sbljlvritz7cg0qmoxakh4ui4tx8 803451 803450 2021-11-16T10:26:17Z en>Fayenatic london 0 update category link 803451 Scribunto text/plain local p = {} local args = {} local origArgs = {} local root local empty_row_categories = {} local category_in_empty_row_pattern = '%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]' local has_rows = false local function fixChildBoxes(sval, tt) local function notempty( s ) return s and s:match( '%S' ) end if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval -- start moving templatestyles and categories inside of table rows local slast = '' while slast ~= s do slast = s s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*%]%])', '%2%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(\127[^\127]*UNIQ%-%-templatestyles%-%x+%-QINU[^\127]*\127)', '%2%1') end -- end moving templatestyles and categories inside of table rows s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end -- the next two lines add a newline at the end of lists for the PHP parser -- [[Special:Diff/849054481]] -- remove when [[:phab:T191516]] is fixed or OBE s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1') s = mw.ustring.gsub(s, '^(%{%|)', '\n%1') return s else return sval end end -- Cleans empty tables local function cleanInfobox() root = tostring(root) if has_rows == false then root = mw.ustring.gsub(root, '<table[^<>]*>%s*</table>', '') end end -- Returns the union of the values of two tables, as a sequence. local function union(t1, t2) local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local function getArgNums(prefix) local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. local function addRow(rowArgs) if rowArgs.header and rowArgs.header ~= '_BLANK_' then has_rows = true root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :tag('th') :attr('colspan', '2') :addClass('infobox-header') :addClass(rowArgs.class) :addClass(args.headerclass) -- @deprecated next; target .infobox-<name> .infobox-header :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) if rowArgs.data then root:wikitext( '[[Category:Pages which use infobox templates with ignored data cells]]' ) end elseif rowArgs.data and rowArgs.data:gsub( category_in_empty_row_pattern, '' ):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) if rowArgs.label then row :tag('th') :attr('scope', 'row') :addClass('infobox-label') -- @deprecated next; target .infobox-<name> .infobox-label :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') dataCell :attr('colspan', not rowArgs.label and '2' or nil) :addClass(not rowArgs.label and 'infobox-full-data' or 'infobox-data') :addClass(rowArgs.class) -- @deprecated next; target .infobox-<name> .infobox(-full)-data :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.data, 'td')) else table.insert(empty_row_categories, rowArgs.data or '') end end local function renderTitle() if not args.title then return end has_rows = true root :tag('caption') :addClass('infobox-title') :addClass(args.titleclass) -- @deprecated next; target .infobox-<name> .infobox-title :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end has_rows = true root :tag('tr') :tag('th') :attr('colspan', '2') :addClass('infobox-above') :addClass(args.aboveclass) -- @deprecated next; target .infobox-<name> .infobox-above :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end has_rows = true root :tag('tr') :tag('td') :attr('colspan', '2') :addClass('infobox-below') :addClass(args.belowclass) -- @deprecated next; target .infobox-<name> .infobox-below :cssText(args.belowstyle) :wikitext(fixChildBoxes(args.below,'td')) end local function addSubheaderRow(subheaderArgs) if subheaderArgs.data and subheaderArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(subheaderArgs.rowclass) local dataCell = row:tag('td') dataCell :attr('colspan', '2') :addClass('infobox-subheader') :addClass(subheaderArgs.class) :cssText(subheaderArgs.datastyle) :cssText(subheaderArgs.rowcellstyle) :wikitext(fixChildBoxes(subheaderArgs.data, 'td')) else table.insert(empty_row_categories, subheaderArgs.data or '') end end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addSubheaderRow({ data = args['subheader' .. tostring(num)], -- @deprecated next; target .infobox-<name> .infobox-subheader datastyle = args.subheaderstyle, rowcellstyle = args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function addImageRow(imageArgs) if imageArgs.data and imageArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(imageArgs.rowclass) local dataCell = row:tag('td') dataCell :attr('colspan', '2') :addClass('infobox-image') :addClass(imageArgs.class) :cssText(imageArgs.datastyle) :wikitext(fixChildBoxes(imageArgs.data, 'td')) else table.insert(empty_row_categories, imageArgs.data or '') end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :addClass('infobox-caption') -- @deprecated next; target .infobox-<name> .infobox-caption :cssText(args.captionstyle) :wikitext(caption) end addImageRow({ data = tostring(data), -- @deprecated next; target .infobox-<name> .infobox-image datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end -- When autoheaders are turned on, preprocesses the rows local function preprocessRows() if not args.autoheaders then return end local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) local lastheader for k, num in ipairs(rownums) do if args['header' .. tostring(num)] then if lastheader then args['header' .. tostring(lastheader)] = nil end lastheader = num elseif args['data' .. tostring(num)] and args['data' .. tostring(num)]:gsub( category_in_empty_row_pattern, '' ):match('^%S') then local data = args['data' .. tostring(num)] if data:gsub(category_in_empty_row_pattern, ''):match('%S') then lastheader = nil end end end if lastheader then args['header' .. tostring(lastheader)] = nil end end -- Gets the union of the header and data argument numbers, -- and renders them all in order local function renderRows() local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], -- @deprecated next; target .infobox-<name> rowclass rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end has_rows = true root :tag('tr') :tag('td') :attr('colspan', '2') :addClass('infobox-navbar') :wikitext(require('Module:Navbar')._navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end -- Categories in otherwise empty rows are collected in empty_row_categories. -- This function adds them to the module output. It is not affected by -- args.decat because this module should not prevent module-external categories -- from rendering. local function renderEmptyRowCategories() for _, s in ipairs(empty_row_categories) do root:wikitext(s) end end -- Render tracking categories. args.decat == turns off tracking categories. local function renderTrackingCategories() if args.decat == 'yes' then return end if args.child == 'yes' then if args.title then root:wikitext( '[[Category:Pages using embedded infobox templates with the title parameter]]' ) end elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles using infobox templates with no data rows]]') end end --[=[ Loads the templatestyles for the infobox. TODO: FINISH loading base templatestyles here rather than in MediaWiki:Common.css. There are 4-5000 pages with 'raw' infobox tables. See [[Mediawiki_talk:Common.css/to_do#Infobox]] and/or come help :). When we do this we should clean up the inline CSS below too. Will have to do some bizarre conversion category like with sidebar. ]=] local function loadTemplateStyles() local frame = mw.getCurrentFrame() -- See function description local base_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = 'Module:Infobox/styles.css' } } local templatestyles = '' if args['templatestyles'] then templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['templatestyles'] } } end local child_templatestyles = '' if args['child templatestyles'] then child_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['child templatestyles'] } } end local grandchild_templatestyles = '' if args['grandchild templatestyles'] then grandchild_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['grandchild templatestyles'] } } end return table.concat({ base_templatestyles, -- see function description templatestyles, child_templatestyles, grandchild_templatestyles }) end -- common functions between the child and non child cases local function structure_infobox_common() renderSubheaders() renderImages() preprocessRows() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderEmptyRowCategories() renderTrackingCategories() cleanInfobox() end -- Specify the overall layout of the infobox, with special settings if the -- infobox is used as a 'child' inside another infobox. local function _infobox() if args.child ~= 'yes' then root = mw.html.create('table') root :addClass(args.subbox == 'yes' and 'infobox-subbox' or 'infobox') :addClass(args.bodyclass) -- @deprecated next; target .infobox-<name> :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end structure_infobox_common() return loadTemplateStyles() .. root end -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. local function preprocessSingleArg(argName) if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end -- Assign the parameters with the given prefixes to the args table, in order, in -- batches of the step size specified. This is to prevent references etc. from -- appearing in the wrong order. The prefixTable should be an array containing -- tables, each of which has two possible fields, a "prefix" string and a -- "depend" table. The function always parses parameters containing the "prefix" -- string, but only parses parameters in the "depend" table if the prefix -- parameter is present and non-blank. local function preprocessArgs(prefixTable, step) if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present -- and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then -- Do another loop if any arguments are found, even blank ones. moreArgumentsExist = true preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present -- and not blank, or we are processing "prefix1" and "prefix" is -- present and not blank, and if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end -- Parse the data parameters in the same order that the old {{infobox}} did, so -- that references etc. will display in the expected places. Parameters that -- depend on another parameter are only processed if that parameter is present, -- to avoid phantom references appearing in article reference lists. local function parseDataParameters() preprocessSingleArg('autoheaders') preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') -- different behaviour for italics if blank or absent args['italic title'] = origArgs['italic title'] preprocessSingleArg('decat') preprocessSingleArg('templatestyles') preprocessSingleArg('child templatestyles') preprocessSingleArg('grandchild templatestyles') end -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. function p.infobox(frame) if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end parseDataParameters() return _infobox() end -- For calling via #invoke within a template function p.infoboxTemplate(frame) origArgs = {} for k,v in pairs(frame.args) do origArgs[k] = mw.text.trim(v) end parseDataParameters() return _infobox() end return p neykqi4g4ir7sm57csrncxrrv016oun 803452 803451 2021-11-16T10:29:12Z en>Fayenatic london 0 update category link 803452 Scribunto text/plain local p = {} local args = {} local origArgs = {} local root local empty_row_categories = {} local category_in_empty_row_pattern = '%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]' local has_rows = false local function fixChildBoxes(sval, tt) local function notempty( s ) return s and s:match( '%S' ) end if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval -- start moving templatestyles and categories inside of table rows local slast = '' while slast ~= s do slast = s s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*%]%])', '%2%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(\127[^\127]*UNIQ%-%-templatestyles%-%x+%-QINU[^\127]*\127)', '%2%1') end -- end moving templatestyles and categories inside of table rows s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end -- the next two lines add a newline at the end of lists for the PHP parser -- [[Special:Diff/849054481]] -- remove when [[:phab:T191516]] is fixed or OBE s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1') s = mw.ustring.gsub(s, '^(%{%|)', '\n%1') return s else return sval end end -- Cleans empty tables local function cleanInfobox() root = tostring(root) if has_rows == false then root = mw.ustring.gsub(root, '<table[^<>]*>%s*</table>', '') end end -- Returns the union of the values of two tables, as a sequence. local function union(t1, t2) local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local function getArgNums(prefix) local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. local function addRow(rowArgs) if rowArgs.header and rowArgs.header ~= '_BLANK_' then has_rows = true root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :tag('th') :attr('colspan', '2') :addClass('infobox-header') :addClass(rowArgs.class) :addClass(args.headerclass) -- @deprecated next; target .infobox-<name> .infobox-header :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) if rowArgs.data then root:wikitext( '[[Category:Pages using infobox templates with ignored data cells]]' ) end elseif rowArgs.data and rowArgs.data:gsub( category_in_empty_row_pattern, '' ):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) if rowArgs.label then row :tag('th') :attr('scope', 'row') :addClass('infobox-label') -- @deprecated next; target .infobox-<name> .infobox-label :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') dataCell :attr('colspan', not rowArgs.label and '2' or nil) :addClass(not rowArgs.label and 'infobox-full-data' or 'infobox-data') :addClass(rowArgs.class) -- @deprecated next; target .infobox-<name> .infobox(-full)-data :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.data, 'td')) else table.insert(empty_row_categories, rowArgs.data or '') end end local function renderTitle() if not args.title then return end has_rows = true root :tag('caption') :addClass('infobox-title') :addClass(args.titleclass) -- @deprecated next; target .infobox-<name> .infobox-title :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end has_rows = true root :tag('tr') :tag('th') :attr('colspan', '2') :addClass('infobox-above') :addClass(args.aboveclass) -- @deprecated next; target .infobox-<name> .infobox-above :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end has_rows = true root :tag('tr') :tag('td') :attr('colspan', '2') :addClass('infobox-below') :addClass(args.belowclass) -- @deprecated next; target .infobox-<name> .infobox-below :cssText(args.belowstyle) :wikitext(fixChildBoxes(args.below,'td')) end local function addSubheaderRow(subheaderArgs) if subheaderArgs.data and subheaderArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(subheaderArgs.rowclass) local dataCell = row:tag('td') dataCell :attr('colspan', '2') :addClass('infobox-subheader') :addClass(subheaderArgs.class) :cssText(subheaderArgs.datastyle) :cssText(subheaderArgs.rowcellstyle) :wikitext(fixChildBoxes(subheaderArgs.data, 'td')) else table.insert(empty_row_categories, subheaderArgs.data or '') end end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addSubheaderRow({ data = args['subheader' .. tostring(num)], -- @deprecated next; target .infobox-<name> .infobox-subheader datastyle = args.subheaderstyle, rowcellstyle = args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function addImageRow(imageArgs) if imageArgs.data and imageArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(imageArgs.rowclass) local dataCell = row:tag('td') dataCell :attr('colspan', '2') :addClass('infobox-image') :addClass(imageArgs.class) :cssText(imageArgs.datastyle) :wikitext(fixChildBoxes(imageArgs.data, 'td')) else table.insert(empty_row_categories, imageArgs.data or '') end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :addClass('infobox-caption') -- @deprecated next; target .infobox-<name> .infobox-caption :cssText(args.captionstyle) :wikitext(caption) end addImageRow({ data = tostring(data), -- @deprecated next; target .infobox-<name> .infobox-image datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end -- When autoheaders are turned on, preprocesses the rows local function preprocessRows() if not args.autoheaders then return end local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) local lastheader for k, num in ipairs(rownums) do if args['header' .. tostring(num)] then if lastheader then args['header' .. tostring(lastheader)] = nil end lastheader = num elseif args['data' .. tostring(num)] and args['data' .. tostring(num)]:gsub( category_in_empty_row_pattern, '' ):match('^%S') then local data = args['data' .. tostring(num)] if data:gsub(category_in_empty_row_pattern, ''):match('%S') then lastheader = nil end end end if lastheader then args['header' .. tostring(lastheader)] = nil end end -- Gets the union of the header and data argument numbers, -- and renders them all in order local function renderRows() local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], -- @deprecated next; target .infobox-<name> rowclass rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end has_rows = true root :tag('tr') :tag('td') :attr('colspan', '2') :addClass('infobox-navbar') :wikitext(require('Module:Navbar')._navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end -- Categories in otherwise empty rows are collected in empty_row_categories. -- This function adds them to the module output. It is not affected by -- args.decat because this module should not prevent module-external categories -- from rendering. local function renderEmptyRowCategories() for _, s in ipairs(empty_row_categories) do root:wikitext(s) end end -- Render tracking categories. args.decat == turns off tracking categories. local function renderTrackingCategories() if args.decat == 'yes' then return end if args.child == 'yes' then if args.title then root:wikitext( '[[Category:Pages using embedded infobox templates with the title parameter]]' ) end elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles using infobox templates with no data rows]]') end end --[=[ Loads the templatestyles for the infobox. TODO: FINISH loading base templatestyles here rather than in MediaWiki:Common.css. There are 4-5000 pages with 'raw' infobox tables. See [[Mediawiki_talk:Common.css/to_do#Infobox]] and/or come help :). When we do this we should clean up the inline CSS below too. Will have to do some bizarre conversion category like with sidebar. ]=] local function loadTemplateStyles() local frame = mw.getCurrentFrame() -- See function description local base_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = 'Module:Infobox/styles.css' } } local templatestyles = '' if args['templatestyles'] then templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['templatestyles'] } } end local child_templatestyles = '' if args['child templatestyles'] then child_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['child templatestyles'] } } end local grandchild_templatestyles = '' if args['grandchild templatestyles'] then grandchild_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['grandchild templatestyles'] } } end return table.concat({ base_templatestyles, -- see function description templatestyles, child_templatestyles, grandchild_templatestyles }) end -- common functions between the child and non child cases local function structure_infobox_common() renderSubheaders() renderImages() preprocessRows() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderEmptyRowCategories() renderTrackingCategories() cleanInfobox() end -- Specify the overall layout of the infobox, with special settings if the -- infobox is used as a 'child' inside another infobox. local function _infobox() if args.child ~= 'yes' then root = mw.html.create('table') root :addClass(args.subbox == 'yes' and 'infobox-subbox' or 'infobox') :addClass(args.bodyclass) -- @deprecated next; target .infobox-<name> :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end structure_infobox_common() return loadTemplateStyles() .. root end -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. local function preprocessSingleArg(argName) if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end -- Assign the parameters with the given prefixes to the args table, in order, in -- batches of the step size specified. This is to prevent references etc. from -- appearing in the wrong order. The prefixTable should be an array containing -- tables, each of which has two possible fields, a "prefix" string and a -- "depend" table. The function always parses parameters containing the "prefix" -- string, but only parses parameters in the "depend" table if the prefix -- parameter is present and non-blank. local function preprocessArgs(prefixTable, step) if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present -- and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then -- Do another loop if any arguments are found, even blank ones. moreArgumentsExist = true preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present -- and not blank, or we are processing "prefix1" and "prefix" is -- present and not blank, and if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end -- Parse the data parameters in the same order that the old {{infobox}} did, so -- that references etc. will display in the expected places. Parameters that -- depend on another parameter are only processed if that parameter is present, -- to avoid phantom references appearing in article reference lists. local function parseDataParameters() preprocessSingleArg('autoheaders') preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') -- different behaviour for italics if blank or absent args['italic title'] = origArgs['italic title'] preprocessSingleArg('decat') preprocessSingleArg('templatestyles') preprocessSingleArg('child templatestyles') preprocessSingleArg('grandchild templatestyles') end -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. function p.infobox(frame) if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end parseDataParameters() return _infobox() end -- For calling via #invoke within a template function p.infoboxTemplate(frame) origArgs = {} for k,v in pairs(frame.args) do origArgs[k] = mw.text.trim(v) end parseDataParameters() return _infobox() end return p dqqnr7l5kr5hmj0mxjqtzu7mvoyat9b 803453 803452 2021-11-16T10:29:48Z en>Fayenatic london 0 spacing 803453 Scribunto text/plain local p = {} local args = {} local origArgs = {} local root local empty_row_categories = {} local category_in_empty_row_pattern = '%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]' local has_rows = false local function fixChildBoxes(sval, tt) local function notempty( s ) return s and s:match( '%S' ) end if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval -- start moving templatestyles and categories inside of table rows local slast = '' while slast ~= s do slast = s s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*%]%])', '%2%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(\127[^\127]*UNIQ%-%-templatestyles%-%x+%-QINU[^\127]*\127)', '%2%1') end -- end moving templatestyles and categories inside of table rows s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end -- the next two lines add a newline at the end of lists for the PHP parser -- [[Special:Diff/849054481]] -- remove when [[:phab:T191516]] is fixed or OBE s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1') s = mw.ustring.gsub(s, '^(%{%|)', '\n%1') return s else return sval end end -- Cleans empty tables local function cleanInfobox() root = tostring(root) if has_rows == false then root = mw.ustring.gsub(root, '<table[^<>]*>%s*</table>', '') end end -- Returns the union of the values of two tables, as a sequence. local function union(t1, t2) local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local function getArgNums(prefix) local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. local function addRow(rowArgs) if rowArgs.header and rowArgs.header ~= '_BLANK_' then has_rows = true root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :tag('th') :attr('colspan', '2') :addClass('infobox-header') :addClass(rowArgs.class) :addClass(args.headerclass) -- @deprecated next; target .infobox-<name> .infobox-header :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) if rowArgs.data then root:wikitext( '[[Category:Pages using infobox templates with ignored data cells]]' ) end elseif rowArgs.data and rowArgs.data:gsub( category_in_empty_row_pattern, '' ):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) if rowArgs.label then row :tag('th') :attr('scope', 'row') :addClass('infobox-label') -- @deprecated next; target .infobox-<name> .infobox-label :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') dataCell :attr('colspan', not rowArgs.label and '2' or nil) :addClass(not rowArgs.label and 'infobox-full-data' or 'infobox-data') :addClass(rowArgs.class) -- @deprecated next; target .infobox-<name> .infobox(-full)-data :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.data, 'td')) else table.insert(empty_row_categories, rowArgs.data or '') end end local function renderTitle() if not args.title then return end has_rows = true root :tag('caption') :addClass('infobox-title') :addClass(args.titleclass) -- @deprecated next; target .infobox-<name> .infobox-title :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end has_rows = true root :tag('tr') :tag('th') :attr('colspan', '2') :addClass('infobox-above') :addClass(args.aboveclass) -- @deprecated next; target .infobox-<name> .infobox-above :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end has_rows = true root :tag('tr') :tag('td') :attr('colspan', '2') :addClass('infobox-below') :addClass(args.belowclass) -- @deprecated next; target .infobox-<name> .infobox-below :cssText(args.belowstyle) :wikitext(fixChildBoxes(args.below,'td')) end local function addSubheaderRow(subheaderArgs) if subheaderArgs.data and subheaderArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(subheaderArgs.rowclass) local dataCell = row:tag('td') dataCell :attr('colspan', '2') :addClass('infobox-subheader') :addClass(subheaderArgs.class) :cssText(subheaderArgs.datastyle) :cssText(subheaderArgs.rowcellstyle) :wikitext(fixChildBoxes(subheaderArgs.data, 'td')) else table.insert(empty_row_categories, subheaderArgs.data or '') end end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addSubheaderRow({ data = args['subheader' .. tostring(num)], -- @deprecated next; target .infobox-<name> .infobox-subheader datastyle = args.subheaderstyle, rowcellstyle = args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function addImageRow(imageArgs) if imageArgs.data and imageArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true local row = root:tag('tr') row:addClass(imageArgs.rowclass) local dataCell = row:tag('td') dataCell :attr('colspan', '2') :addClass('infobox-image') :addClass(imageArgs.class) :cssText(imageArgs.datastyle) :wikitext(fixChildBoxes(imageArgs.data, 'td')) else table.insert(empty_row_categories, imageArgs.data or '') end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :addClass('infobox-caption') -- @deprecated next; target .infobox-<name> .infobox-caption :cssText(args.captionstyle) :wikitext(caption) end addImageRow({ data = tostring(data), -- @deprecated next; target .infobox-<name> .infobox-image datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end -- When autoheaders are turned on, preprocesses the rows local function preprocessRows() if not args.autoheaders then return end local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) local lastheader for k, num in ipairs(rownums) do if args['header' .. tostring(num)] then if lastheader then args['header' .. tostring(lastheader)] = nil end lastheader = num elseif args['data' .. tostring(num)] and args['data' .. tostring(num)]:gsub( category_in_empty_row_pattern, '' ):match('^%S') then local data = args['data' .. tostring(num)] if data:gsub(category_in_empty_row_pattern, ''):match('%S') then lastheader = nil end end end if lastheader then args['header' .. tostring(lastheader)] = nil end end -- Gets the union of the header and data argument numbers, -- and renders them all in order local function renderRows() local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], -- @deprecated next; target .infobox-<name> rowclass rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end has_rows = true root :tag('tr') :tag('td') :attr('colspan', '2') :addClass('infobox-navbar') :wikitext(require('Module:Navbar')._navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(mw.getCurrentFrame():expandTemplate({title = 'italic title'})) end end -- Categories in otherwise empty rows are collected in empty_row_categories. -- This function adds them to the module output. It is not affected by -- args.decat because this module should not prevent module-external categories -- from rendering. local function renderEmptyRowCategories() for _, s in ipairs(empty_row_categories) do root:wikitext(s) end end -- Render tracking categories. args.decat == turns off tracking categories. local function renderTrackingCategories() if args.decat == 'yes' then return end if args.child == 'yes' then if args.title then root:wikitext( '[[Category:Pages using embedded infobox templates with the title parameter]]' ) end elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles using infobox templates with no data rows]]') end end --[=[ Loads the templatestyles for the infobox. TODO: FINISH loading base templatestyles here rather than in MediaWiki:Common.css. There are 4-5000 pages with 'raw' infobox tables. See [[Mediawiki_talk:Common.css/to_do#Infobox]] and/or come help :). When we do this we should clean up the inline CSS below too. Will have to do some bizarre conversion category like with sidebar. ]=] local function loadTemplateStyles() local frame = mw.getCurrentFrame() -- See function description local base_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = 'Module:Infobox/styles.css' } } local templatestyles = '' if args['templatestyles'] then templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['templatestyles'] } } end local child_templatestyles = '' if args['child templatestyles'] then child_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['child templatestyles'] } } end local grandchild_templatestyles = '' if args['grandchild templatestyles'] then grandchild_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['grandchild templatestyles'] } } end return table.concat({ base_templatestyles, -- see function description templatestyles, child_templatestyles, grandchild_templatestyles }) end -- common functions between the child and non child cases local function structure_infobox_common() renderSubheaders() renderImages() preprocessRows() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderEmptyRowCategories() renderTrackingCategories() cleanInfobox() end -- Specify the overall layout of the infobox, with special settings if the -- infobox is used as a 'child' inside another infobox. local function _infobox() if args.child ~= 'yes' then root = mw.html.create('table') root :addClass(args.subbox == 'yes' and 'infobox-subbox' or 'infobox') :addClass(args.bodyclass) -- @deprecated next; target .infobox-<name> :cssText(args.bodystyle) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end structure_infobox_common() return loadTemplateStyles() .. root end -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. local function preprocessSingleArg(argName) if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end -- Assign the parameters with the given prefixes to the args table, in order, in -- batches of the step size specified. This is to prevent references etc. from -- appearing in the wrong order. The prefixTable should be an array containing -- tables, each of which has two possible fields, a "prefix" string and a -- "depend" table. The function always parses parameters containing the "prefix" -- string, but only parses parameters in the "depend" table if the prefix -- parameter is present and non-blank. local function preprocessArgs(prefixTable, step) if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present -- and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then -- Do another loop if any arguments are found, even blank ones. moreArgumentsExist = true preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present -- and not blank, or we are processing "prefix1" and "prefix" is -- present and not blank, and if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end -- Parse the data parameters in the same order that the old {{infobox}} did, so -- that references etc. will display in the expected places. Parameters that -- depend on another parameter are only processed if that parameter is present, -- to avoid phantom references appearing in article reference lists. local function parseDataParameters() preprocessSingleArg('autoheaders') preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') -- different behaviour for italics if blank or absent args['italic title'] = origArgs['italic title'] preprocessSingleArg('decat') preprocessSingleArg('templatestyles') preprocessSingleArg('child templatestyles') preprocessSingleArg('grandchild templatestyles') end -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. function p.infobox(frame) if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end parseDataParameters() return _infobox() end -- For calling via #invoke within a template function p.infoboxTemplate(frame) origArgs = {} for k,v in pairs(frame.args) do origArgs[k] = mw.text.trim(v) end parseDataParameters() return _infobox() end return p 0nrwbmsjupox6pg5hh6nvlk9r7dcw81 803454 803453 2022-12-20T20:41:06Z en>Izno 0 support finding plainlist in infobox class fields 803454 Scribunto text/plain local p = {} local args = {} local origArgs = {} local root local empty_row_categories = {} local category_in_empty_row_pattern = '%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]' local has_rows = false local lists = { plainlist_t = { patterns = { '^plainlist$', '%splainlist$', '^plainlist%s', '%splainlist%s' }, found = false, styles = 'Plainlist/styles.css' } -- TODO: hlist_t goes here when ready to be TStyled, plus relevant -- implementations below } local function has_list_class(args_to_check) for _, list in pairs(lists) do if not list.found then for _, arg in pairs(args_to_check) do for _, pattern in ipairs(list.patterns) do if mw.ustring.find(arg or '', pattern) then list.found = true break end end if list.found then break end end end end end local function fixChildBoxes(sval, tt) local function notempty( s ) return s and s:match( '%S' ) end if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval -- start moving templatestyles and categories inside of table rows local slast = '' while slast ~= s do slast = s s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*%]%])', '%2%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(\127[^\127]*UNIQ%-%-templatestyles%-%x+%-QINU[^\127]*\127)', '%2%1') end -- end moving templatestyles and categories inside of table rows s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end -- the next two lines add a newline at the end of lists for the PHP parser -- [[Special:Diff/849054481]] -- remove when [[:phab:T191516]] is fixed or OBE s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1') s = mw.ustring.gsub(s, '^(%{%|)', '\n%1') return s else return sval end end -- Cleans empty tables local function cleanInfobox() root = tostring(root) if has_rows == false then root = mw.ustring.gsub(root, '<table[^<>]*>%s*</table>', '') end end -- Returns the union of the values of two tables, as a sequence. local function union(t1, t2) local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local function getArgNums(prefix) local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. local function addRow(rowArgs) if rowArgs.header and rowArgs.header ~= '_BLANK_' then has_rows = true has_list_class({ rowArgs.rowclass, rowArgs.class, args.headerclass }) root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :tag('th') :attr('colspan', '2') :addClass('infobox-header') :addClass(rowArgs.class) :addClass(args.headerclass) -- @deprecated next; target .infobox-<name> .infobox-header :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) if rowArgs.data then root:wikitext( '[[Category:Pages using infobox templates with ignored data cells]]' ) end elseif rowArgs.data and rowArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true has_list_class({ rowArgs.rowclass, rowArgs.class }) local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) if rowArgs.label then row :tag('th') :attr('scope', 'row') :addClass('infobox-label') -- @deprecated next; target .infobox-<name> .infobox-label :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') dataCell :attr('colspan', not rowArgs.label and '2' or nil) :addClass(not rowArgs.label and 'infobox-full-data' or 'infobox-data') :addClass(rowArgs.class) -- @deprecated next; target .infobox-<name> .infobox(-full)-data :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.data, 'td')) else table.insert(empty_row_categories, rowArgs.data or '') end end local function renderTitle() if not args.title then return end has_rows = true has_list_class({args.titleclass}) root :tag('caption') :addClass('infobox-title') :addClass(args.titleclass) -- @deprecated next; target .infobox-<name> .infobox-title :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end has_rows = true has_list_class({ args.aboveclass }) root :tag('tr') :tag('th') :attr('colspan', '2') :addClass('infobox-above') :addClass(args.aboveclass) -- @deprecated next; target .infobox-<name> .infobox-above :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end has_rows = true has_list_class({ args.belowclass }) root :tag('tr') :tag('td') :attr('colspan', '2') :addClass('infobox-below') :addClass(args.belowclass) -- @deprecated next; target .infobox-<name> .infobox-below :cssText(args.belowstyle) :wikitext(fixChildBoxes(args.below,'td')) end local function addSubheaderRow(subheaderArgs) if subheaderArgs.data and subheaderArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true has_list_class({ subheaderArgs.rowclass, subheaderArgs.class }) local row = root:tag('tr') row:addClass(subheaderArgs.rowclass) local dataCell = row:tag('td') dataCell :attr('colspan', '2') :addClass('infobox-subheader') :addClass(subheaderArgs.class) :cssText(subheaderArgs.datastyle) :cssText(subheaderArgs.rowcellstyle) :wikitext(fixChildBoxes(subheaderArgs.data, 'td')) else table.insert(empty_row_categories, subheaderArgs.data or '') end end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addSubheaderRow({ data = args['subheader' .. tostring(num)], -- @deprecated next; target .infobox-<name> .infobox-subheader datastyle = args.subheaderstyle, rowcellstyle = args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function addImageRow(imageArgs) if imageArgs.data and imageArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true has_list_class({ imageArgs.rowclass, imageArgs.class }) local row = root:tag('tr') row:addClass(imageArgs.rowclass) local dataCell = row:tag('td') dataCell :attr('colspan', '2') :addClass('infobox-image') :addClass(imageArgs.class) :cssText(imageArgs.datastyle) :wikitext(fixChildBoxes(imageArgs.data, 'td')) else table.insert(empty_row_categories, imageArgs.data or '') end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :addClass('infobox-caption') -- @deprecated next; target .infobox-<name> .infobox-caption :cssText(args.captionstyle) :wikitext(caption) end addImageRow({ data = tostring(data), -- @deprecated next; target .infobox-<name> .infobox-image datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end -- When autoheaders are turned on, preprocesses the rows local function preprocessRows() if not args.autoheaders then return end local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) local lastheader for k, num in ipairs(rownums) do if args['header' .. tostring(num)] then if lastheader then args['header' .. tostring(lastheader)] = nil end lastheader = num elseif args['data' .. tostring(num)] and args['data' .. tostring(num)]:gsub( category_in_empty_row_pattern, '' ):match('^%S') then local data = args['data' .. tostring(num)] if data:gsub(category_in_empty_row_pattern, ''):match('%S') then lastheader = nil end end end if lastheader then args['header' .. tostring(lastheader)] = nil end end -- Gets the union of the header and data argument numbers, -- and renders them all in order local function renderRows() local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], -- @deprecated next; target .infobox-<name> rowclass rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end has_rows = true root :tag('tr') :tag('td') :attr('colspan', '2') :addClass('infobox-navbar') :wikitext(require('Module:Navbar')._navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(require('Module:Italic title')._main({})) end end -- Categories in otherwise empty rows are collected in empty_row_categories. -- This function adds them to the module output. It is not affected by -- args.decat because this module should not prevent module-external categories -- from rendering. local function renderEmptyRowCategories() for _, s in ipairs(empty_row_categories) do root:wikitext(s) end end -- Render tracking categories. args.decat == turns off tracking categories. local function renderTrackingCategories() if args.decat == 'yes' then return end if args.child == 'yes' then if args.title then root:wikitext( '[[Category:Pages using embedded infobox templates with the title parameter]]' ) end elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles using infobox templates with no data rows]]') end end --[=[ Loads the templatestyles for the infobox. TODO: FINISH loading base templatestyles here rather than in MediaWiki:Common.css. There are 4-5000 pages with 'raw' infobox tables. See [[Mediawiki_talk:Common.css/to_do#Infobox]] and/or come help :). When we do this we should clean up the inline CSS below too. Will have to do some bizarre conversion category like with sidebar. ]=] local function loadTemplateStyles() local frame = mw.getCurrentFrame() -- See function description local base_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = 'Module:Infobox/styles.css' } } local templatestyles = '' if args['templatestyles'] then templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['templatestyles'] } } end local child_templatestyles = '' if args['child templatestyles'] then child_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['child templatestyles'] } } end local grandchild_templatestyles = '' if args['grandchild templatestyles'] then grandchild_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['grandchild templatestyles'] } } end local plainlist_templatestyles = '' if lists.plainlist_t.found then plainlist_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = lists.plainlist_t.styles } } end return table.concat({ base_templatestyles, -- see function description templatestyles, child_templatestyles, grandchild_templatestyles, plainlist_templatestyles }) end -- common functions between the child and non child cases local function structure_infobox_common() renderSubheaders() renderImages() preprocessRows() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderEmptyRowCategories() renderTrackingCategories() cleanInfobox() end -- Specify the overall layout of the infobox, with special settings if the -- infobox is used as a 'child' inside another infobox. local function _infobox() if args.child ~= 'yes' then root = mw.html.create('table') root :addClass(args.subbox == 'yes' and 'infobox-subbox' or 'infobox') :addClass(args.bodyclass) -- @deprecated next; target .infobox-<name> :cssText(args.bodystyle) has_list_class({ args.bodyclass }) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end structure_infobox_common() return loadTemplateStyles() .. root end -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. local function preprocessSingleArg(argName) if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end -- Assign the parameters with the given prefixes to the args table, in order, in -- batches of the step size specified. This is to prevent references etc. from -- appearing in the wrong order. The prefixTable should be an array containing -- tables, each of which has two possible fields, a "prefix" string and a -- "depend" table. The function always parses parameters containing the "prefix" -- string, but only parses parameters in the "depend" table if the prefix -- parameter is present and non-blank. local function preprocessArgs(prefixTable, step) if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present -- and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then -- Do another loop if any arguments are found, even blank ones. moreArgumentsExist = true preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present -- and not blank, or we are processing "prefix1" and "prefix" is -- present and not blank, and if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end -- Parse the data parameters in the same order that the old {{infobox}} did, so -- that references etc. will display in the expected places. Parameters that -- depend on another parameter are only processed if that parameter is present, -- to avoid phantom references appearing in article reference lists. local function parseDataParameters() preprocessSingleArg('autoheaders') preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') -- different behaviour for italics if blank or absent args['italic title'] = origArgs['italic title'] preprocessSingleArg('decat') preprocessSingleArg('templatestyles') preprocessSingleArg('child templatestyles') preprocessSingleArg('grandchild templatestyles') end -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. function p.infobox(frame) if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end parseDataParameters() return _infobox() end -- For calling via #invoke within a template function p.infoboxTemplate(frame) origArgs = {} for k,v in pairs(frame.args) do origArgs[k] = mw.text.trim(v) end parseDataParameters() return _infobox() end return p n7l5q18lc0pbmi0pb1ynzmkgxi2kqcv 803455 803454 2022-12-27T21:29:12Z en>Izno 0 merge hlist here 803455 Scribunto text/plain local p = {} local args = {} local origArgs = {} local root local empty_row_categories = {} local category_in_empty_row_pattern = '%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]' local has_rows = false local lists = { plainlist_t = { patterns = { '^plainlist$', '%splainlist$', '^plainlist%s', '%splainlist%s' }, found = false, styles = 'Plainlist/styles.css' }, hlist_t = { patterns = { '^hlist$', '%shlist$', '^hlist%s', '%shlist%s' }, found = false, styles = 'Hlist/styles.css' } } local function has_list_class(args_to_check) for _, list in pairs(lists) do if not list.found then for _, arg in pairs(args_to_check) do for _, pattern in ipairs(list.patterns) do if mw.ustring.find(arg or '', pattern) then list.found = true break end end if list.found then break end end end end end local function fixChildBoxes(sval, tt) local function notempty( s ) return s and s:match( '%S' ) end if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval -- start moving templatestyles and categories inside of table rows local slast = '' while slast ~= s do slast = s s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*%]%])', '%2%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(\127[^\127]*UNIQ%-%-templatestyles%-%x+%-QINU[^\127]*\127)', '%2%1') end -- end moving templatestyles and categories inside of table rows s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end -- the next two lines add a newline at the end of lists for the PHP parser -- [[Special:Diff/849054481]] -- remove when [[:phab:T191516]] is fixed or OBE s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1') s = mw.ustring.gsub(s, '^(%{%|)', '\n%1') return s else return sval end end -- Cleans empty tables local function cleanInfobox() root = tostring(root) if has_rows == false then root = mw.ustring.gsub(root, '<table[^<>]*>%s*</table>', '') end end -- Returns the union of the values of two tables, as a sequence. local function union(t1, t2) local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local function getArgNums(prefix) local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. local function addRow(rowArgs) if rowArgs.header and rowArgs.header ~= '_BLANK_' then has_rows = true has_list_class({ rowArgs.rowclass, rowArgs.class, args.headerclass }) root :tag('tr') :addClass(rowArgs.rowclass) :cssText(rowArgs.rowstyle) :tag('th') :attr('colspan', '2') :addClass('infobox-header') :addClass(rowArgs.class) :addClass(args.headerclass) -- @deprecated next; target .infobox-<name> .infobox-header :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) if rowArgs.data then root:wikitext( '[[Category:Pages using infobox templates with ignored data cells]]' ) end elseif rowArgs.data and rowArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true has_list_class({ rowArgs.rowclass, rowArgs.class }) local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) if rowArgs.label then row :tag('th') :attr('scope', 'row') :addClass('infobox-label') -- @deprecated next; target .infobox-<name> .infobox-label :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() end local dataCell = row:tag('td') dataCell :attr('colspan', not rowArgs.label and '2' or nil) :addClass(not rowArgs.label and 'infobox-full-data' or 'infobox-data') :addClass(rowArgs.class) -- @deprecated next; target .infobox-<name> .infobox(-full)-data :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.data, 'td')) else table.insert(empty_row_categories, rowArgs.data or '') end end local function renderTitle() if not args.title then return end has_rows = true has_list_class({args.titleclass}) root :tag('caption') :addClass('infobox-title') :addClass(args.titleclass) -- @deprecated next; target .infobox-<name> .infobox-title :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end has_rows = true has_list_class({ args.aboveclass }) root :tag('tr') :tag('th') :attr('colspan', '2') :addClass('infobox-above') :addClass(args.aboveclass) -- @deprecated next; target .infobox-<name> .infobox-above :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end has_rows = true has_list_class({ args.belowclass }) root :tag('tr') :tag('td') :attr('colspan', '2') :addClass('infobox-below') :addClass(args.belowclass) -- @deprecated next; target .infobox-<name> .infobox-below :cssText(args.belowstyle) :wikitext(fixChildBoxes(args.below,'td')) end local function addSubheaderRow(subheaderArgs) if subheaderArgs.data and subheaderArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true has_list_class({ subheaderArgs.rowclass, subheaderArgs.class }) local row = root:tag('tr') row:addClass(subheaderArgs.rowclass) local dataCell = row:tag('td') dataCell :attr('colspan', '2') :addClass('infobox-subheader') :addClass(subheaderArgs.class) :cssText(subheaderArgs.datastyle) :cssText(subheaderArgs.rowcellstyle) :wikitext(fixChildBoxes(subheaderArgs.data, 'td')) else table.insert(empty_row_categories, subheaderArgs.data or '') end end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addSubheaderRow({ data = args['subheader' .. tostring(num)], -- @deprecated next; target .infobox-<name> .infobox-subheader datastyle = args.subheaderstyle, rowcellstyle = args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function addImageRow(imageArgs) if imageArgs.data and imageArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true has_list_class({ imageArgs.rowclass, imageArgs.class }) local row = root:tag('tr') row:addClass(imageArgs.rowclass) local dataCell = row:tag('td') dataCell :attr('colspan', '2') :addClass('infobox-image') :addClass(imageArgs.class) :cssText(imageArgs.datastyle) :wikitext(fixChildBoxes(imageArgs.data, 'td')) else table.insert(empty_row_categories, imageArgs.data or '') end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :addClass('infobox-caption') -- @deprecated next; target .infobox-<name> .infobox-caption :cssText(args.captionstyle) :wikitext(caption) end addImageRow({ data = tostring(data), -- @deprecated next; target .infobox-<name> .infobox-image datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end -- When autoheaders are turned on, preprocesses the rows local function preprocessRows() if not args.autoheaders then return end local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) local lastheader for k, num in ipairs(rownums) do if args['header' .. tostring(num)] then if lastheader then args['header' .. tostring(lastheader)] = nil end lastheader = num elseif args['data' .. tostring(num)] and args['data' .. tostring(num)]:gsub( category_in_empty_row_pattern, '' ):match('^%S') then local data = args['data' .. tostring(num)] if data:gsub(category_in_empty_row_pattern, ''):match('%S') then lastheader = nil end end end if lastheader then args['header' .. tostring(lastheader)] = nil end end -- Gets the union of the header and data argument numbers, -- and renders them all in order local function renderRows() local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], -- @deprecated next; target .infobox-<name> rowclass rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end has_rows = true root :tag('tr') :tag('td') :attr('colspan', '2') :addClass('infobox-navbar') :wikitext(require('Module:Navbar')._navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(require('Module:Italic title')._main({})) end end -- Categories in otherwise empty rows are collected in empty_row_categories. -- This function adds them to the module output. It is not affected by -- args.decat because this module should not prevent module-external categories -- from rendering. local function renderEmptyRowCategories() for _, s in ipairs(empty_row_categories) do root:wikitext(s) end end -- Render tracking categories. args.decat == turns off tracking categories. local function renderTrackingCategories() if args.decat == 'yes' then return end if args.child == 'yes' then if args.title then root:wikitext( '[[Category:Pages using embedded infobox templates with the title parameter]]' ) end elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles using infobox templates with no data rows]]') end end --[=[ Loads the templatestyles for the infobox. TODO: FINISH loading base templatestyles here rather than in MediaWiki:Common.css. There are 4-5000 pages with 'raw' infobox tables. See [[Mediawiki_talk:Common.css/to_do#Infobox]] and/or come help :). When we do this we should clean up the inline CSS below too. Will have to do some bizarre conversion category like with sidebar. ]=] local function loadTemplateStyles() local frame = mw.getCurrentFrame() local hlist_templatestyles = '' if lists.hlist_t.found then hlist_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = lists.hlist_t.styles } } end local plainlist_templatestyles = '' if lists.plainlist_t.found then plainlist_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = lists.plainlist_t.styles } } end -- See function description local base_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = 'Module:Infobox/styles.css' } } local templatestyles = '' if args['templatestyles'] then templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['templatestyles'] } } end local child_templatestyles = '' if args['child templatestyles'] then child_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['child templatestyles'] } } end local grandchild_templatestyles = '' if args['grandchild templatestyles'] then grandchild_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['grandchild templatestyles'] } } end return table.concat({ -- hlist -> plainlist -> base is best-effort to preserve old Common.css ordering. -- this ordering is not a guarantee because the rows of interest invoking -- each class may not be on a specific page hlist_templatestyles, plainlist_templatestyles, base_templatestyles, templatestyles, child_templatestyles, grandchild_templatestyles }) end -- common functions between the child and non child cases local function structure_infobox_common() renderSubheaders() renderImages() preprocessRows() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderEmptyRowCategories() renderTrackingCategories() cleanInfobox() end -- Specify the overall layout of the infobox, with special settings if the -- infobox is used as a 'child' inside another infobox. local function _infobox() if args.child ~= 'yes' then root = mw.html.create('table') root :addClass(args.subbox == 'yes' and 'infobox-subbox' or 'infobox') :addClass(args.bodyclass) -- @deprecated next; target .infobox-<name> :cssText(args.bodystyle) has_list_class({ args.bodyclass }) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end structure_infobox_common() return loadTemplateStyles() .. root end -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. local function preprocessSingleArg(argName) if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end -- Assign the parameters with the given prefixes to the args table, in order, in -- batches of the step size specified. This is to prevent references etc. from -- appearing in the wrong order. The prefixTable should be an array containing -- tables, each of which has two possible fields, a "prefix" string and a -- "depend" table. The function always parses parameters containing the "prefix" -- string, but only parses parameters in the "depend" table if the prefix -- parameter is present and non-blank. local function preprocessArgs(prefixTable, step) if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present -- and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then -- Do another loop if any arguments are found, even blank ones. moreArgumentsExist = true preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present -- and not blank, or we are processing "prefix1" and "prefix" is -- present and not blank, and if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end -- Parse the data parameters in the same order that the old {{infobox}} did, so -- that references etc. will display in the expected places. Parameters that -- depend on another parameter are only processed if that parameter is present, -- to avoid phantom references appearing in article reference lists. local function parseDataParameters() preprocessSingleArg('autoheaders') preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') -- different behaviour for italics if blank or absent args['italic title'] = origArgs['italic title'] preprocessSingleArg('decat') preprocessSingleArg('templatestyles') preprocessSingleArg('child templatestyles') preprocessSingleArg('grandchild templatestyles') end -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. function p.infobox(frame) if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end parseDataParameters() return _infobox() end -- For calling via #invoke within a template function p.infoboxTemplate(frame) origArgs = {} for k,v in pairs(frame.args) do origArgs[k] = mw.text.trim(v) end parseDataParameters() return _infobox() end return p 1m9tzydwofr8cj9bzzm2am6moool42y 803456 796825 2026-06-08T00:50:22Z en>Zackmann08 0 hiding un-needed blank rows that show up in child infoboxes per [[Template_talk:Infobox#Blank_rows_on_child_infoboxes....|talk page]] 803456 Scribunto text/plain local p = {} local args = {} local origArgs = {} local root local empty_row_categories = {} local category_in_empty_row_pattern = '%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]' local has_rows = false local yesno = require("Module:Yesno") local lists = { plainlist_t = { patterns = { '^plainlist$', '%splainlist$', '^plainlist%s', '%splainlist%s' }, found = false, styles = 'Plainlist/styles.css' }, hlist_t = { patterns = { '^hlist$', '%shlist$', '^hlist%s', '%shlist%s' }, found = false, styles = 'Hlist/styles.css' } } local function has_list_class(args_to_check) for _, list in pairs(lists) do if not list.found then for _, arg in pairs(args_to_check) do for _, pattern in ipairs(list.patterns) do if mw.ustring.find(arg or '', pattern) then list.found = true break end end if list.found then break end end end end end local function isUntitledChildBox(sval) return sval and ( sval:match( '^%s*<%s*[Tt][Rr]' ) or sval:match( '^%s*\127[^\127]*UNIQ%-%-templatestyles%-%x+%-QINU[^\127]*\127%s*<%s*[Tt][Rr]' ) ) end local function fixChildBoxes(sval, tt) local function notempty( s ) return s and s:match( '%S' ) end if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval -- start moving templatestyles and categories inside of table rows local slast = '' while slast ~= s do slast = s s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*%]%])', '%2%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(\127[^\127]*UNIQ%-%-templatestyles%-%x+%-QINU[^\127]*\127)', '%2%1') end -- end moving templatestyles and categories inside of table rows s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end -- the next two lines add a newline at the end of lists for the PHP parser -- [[Special:Diff/849054481]] -- remove when [[:phab:T191516]] is fixed or OBE s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1') s = mw.ustring.gsub(s, '^(%{%|)', '\n%1') return s else return sval end end -- Cleans empty tables local function cleanInfobox() root = tostring(root) if has_rows == false then root = mw.ustring.gsub(root, '<table[^<>]*>%s*</table>', '') end end -- Returns the union of the values of two tables, as a sequence. local function union(t1, t2) local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local function getArgNums(prefix) local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. local function addRow(rowArgs) if rowArgs.header and rowArgs.header ~= '_BLANK_' then has_rows = true has_list_class({ rowArgs.rowclass, rowArgs.class, args.headerclass }) root :tag('tr') :addClass(rowArgs.rowclass) :addClass( isUntitledChildBox( rowArgs.header ) and 'infobox-hiddenrow' or nil ) :cssText(rowArgs.rowstyle) :tag('th') :attr('colspan', '2') :addClass('infobox-header') :addClass(rowArgs.class) :addClass(args.headerclass) -- @deprecated next; target .infobox-<name> .infobox-header :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) if rowArgs.data and not yesno(args.decat) then root:wikitext( '[[Category:Pages using infobox templates with ignored data cells]]' ) end elseif rowArgs.data and rowArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true has_list_class({ rowArgs.rowclass, rowArgs.class }) local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) if rowArgs.label then row :tag('th') :attr('scope', 'row') :addClass('infobox-label') -- @deprecated next; target .infobox-<name> .infobox-label :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() else row:addClass( isUntitledChildBox( rowArgs.data ) and 'infobox-hiddenrow' or nil ) end local dataCell = row:tag('td') dataCell :attr('colspan', not rowArgs.label and '2' or nil) :addClass(not rowArgs.label and 'infobox-full-data' or 'infobox-data') :addClass(rowArgs.class) -- @deprecated next; target .infobox-<name> .infobox(-full)-data :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.data, 'td')) else table.insert(empty_row_categories, rowArgs.data or '') end end local function renderTitle() if not args.title then return end has_rows = true has_list_class({args.titleclass}) root :tag('caption') :addClass('infobox-title') :addClass(args.titleclass) -- @deprecated next; target .infobox-<name> .infobox-title :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end has_rows = true has_list_class({ args.aboveclass }) root :tag('tr') :addClass( isUntitledChildBox( args.above ) and 'infobox-hiddenrow' or nil ) :tag('th') :attr('colspan', '2') :addClass('infobox-above') :addClass(args.aboveclass) -- @deprecated next; target .infobox-<name> .infobox-above :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end has_rows = true has_list_class({ args.belowclass }) root :tag('tr') :addClass( isUntitledChildBox( args.below ) and 'infobox-hiddenrow' or nil ) :tag('td') :attr('colspan', '2') :addClass('infobox-below') :addClass(args.belowclass) -- @deprecated next; target .infobox-<name> .infobox-below :cssText(args.belowstyle) :wikitext(fixChildBoxes(args.below,'td')) end local function addSubheaderRow(subheaderArgs) if subheaderArgs.data and subheaderArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true has_list_class({ subheaderArgs.rowclass, subheaderArgs.class }) local row = root:tag('tr') row:addClass(subheaderArgs.rowclass) row:addClass( isUntitledChildBox( subheaderArgs.data ) and 'infobox-hiddenrow' or nil ) local dataCell = row:tag('td') dataCell :attr('colspan', '2') :addClass('infobox-subheader') :addClass(subheaderArgs.class) :cssText(subheaderArgs.datastyle) :cssText(subheaderArgs.rowcellstyle) :wikitext(fixChildBoxes(subheaderArgs.data, 'td')) else table.insert(empty_row_categories, subheaderArgs.data or '') end end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addSubheaderRow({ data = args['subheader' .. tostring(num)], -- @deprecated next; target .infobox-<name> .infobox-subheader datastyle = args.subheaderstyle, rowcellstyle = args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function addImageRow(imageArgs) if imageArgs.data and imageArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true has_list_class({ imageArgs.rowclass, imageArgs.class }) local row = root:tag('tr') row:addClass(imageArgs.rowclass) row:addClass( isUntitledChildBox( imageArgs.data ) and 'infobox-hiddenrow' or nil ) local dataCell = row:tag('td') dataCell :attr('colspan', '2') :addClass('infobox-image') :addClass(imageArgs.class) :cssText(imageArgs.datastyle) :wikitext(fixChildBoxes(imageArgs.data, 'td')) else table.insert(empty_row_categories, imageArgs.data or '') end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :addClass('infobox-caption') -- @deprecated next; target .infobox-<name> .infobox-caption :cssText(args.captionstyle) :wikitext(caption) end addImageRow({ data = tostring(data), -- @deprecated next; target .infobox-<name> .infobox-image datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end -- When autoheaders are turned on, preprocesses the rows local function preprocessRows() if not args.autoheaders then return end local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) local lastheader for k, num in ipairs(rownums) do if args['header' .. tostring(num)] then if lastheader then args['header' .. tostring(lastheader)] = nil end lastheader = num elseif args['data' .. tostring(num)] and args['data' .. tostring(num)]:gsub( category_in_empty_row_pattern, '' ):match('^%S') then local data = args['data' .. tostring(num)] if data:gsub(category_in_empty_row_pattern, ''):match('%S') then lastheader = nil end end end if lastheader then args['header' .. tostring(lastheader)] = nil end end -- Gets the union of the header and data argument numbers, -- and renders them all in order local function renderRows() local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], -- @deprecated next; target .infobox-<name> rowclass rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end has_rows = true root :tag('tr') :tag('td') :attr('colspan', '2') :addClass('infobox-navbar') :wikitext(require('Module:Navbar')._navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(require('Module:Italic title')._main({})) end end -- Categories in otherwise empty rows are collected in empty_row_categories. -- This function adds them to the module output. It is not affected by -- args.decat because this module should not prevent module-external categories -- from rendering. local function renderEmptyRowCategories() for _, s in ipairs(empty_row_categories) do root:wikitext(s) end end -- Render tracking categories. args.decat == turns off tracking categories. local function renderTrackingCategories() if yesno(args.decat) then return end if args.child == 'yes' then if args.title then root:wikitext( '[[Category:Pages using embedded infobox templates with the title parameter]]' ) end elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles using infobox templates with no data rows]]') end end --[=[ Loads the templatestyles for the infobox. TODO: FINISH loading base templatestyles here rather than in MediaWiki:Common.css. There are 4-5000 pages with 'raw' infobox tables. See [[Mediawiki_talk:Common.css/to_do#Infobox]] and/or come help :). When we do this we should clean up the inline CSS below too. Will have to do some bizarre conversion category like with sidebar. ]=] local function loadTemplateStyles() local frame = mw.getCurrentFrame() local hlist_templatestyles = '' if lists.hlist_t.found then hlist_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = lists.hlist_t.styles } } end local plainlist_templatestyles = '' if lists.plainlist_t.found then plainlist_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = lists.plainlist_t.styles } } end -- See function description local base_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = 'Module:Infobox/styles.css' } } local templatestyles = '' if args['templatestyles'] then templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['templatestyles'] } } end local child_templatestyles = '' if args['child templatestyles'] then child_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['child templatestyles'] } } end local grandchild_templatestyles = '' if args['grandchild templatestyles'] then grandchild_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['grandchild templatestyles'] } } end return table.concat({ -- hlist -> plainlist -> base is best-effort to preserve old Common.css ordering. -- this ordering is not a guarantee because the rows of interest invoking -- each class may not be on a specific page hlist_templatestyles, plainlist_templatestyles, base_templatestyles, templatestyles, child_templatestyles, grandchild_templatestyles }) end -- common functions between the child and non child cases local function structure_infobox_common() renderSubheaders() renderImages() preprocessRows() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderEmptyRowCategories() renderTrackingCategories() cleanInfobox() end -- Specify the overall layout of the infobox, with special settings if the -- infobox is used as a 'child' inside another infobox. local function _infobox() if args.child ~= 'yes' then root = mw.html.create('table') root :addClass(args.subbox == 'yes' and 'infobox-subbox' or 'infobox') :addClass(args.bodyclass) -- @deprecated next; target .infobox-<name> :cssText(args.bodystyle) has_list_class({ args.bodyclass }) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end structure_infobox_common() return loadTemplateStyles() .. root end -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. local function preprocessSingleArg(argName) if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end -- Assign the parameters with the given prefixes to the args table, in order, in -- batches of the step size specified. This is to prevent references etc. from -- appearing in the wrong order. The prefixTable should be an array containing -- tables, each of which has two possible fields, a "prefix" string and a -- "depend" table. The function always parses parameters containing the "prefix" -- string, but only parses parameters in the "depend" table if the prefix -- parameter is present and non-blank. local function preprocessArgs(prefixTable, step) if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present -- and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then -- Do another loop if any arguments are found, even blank ones. moreArgumentsExist = true preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present -- and not blank, or we are processing "prefix1" and "prefix" is -- present and not blank, and if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end -- Parse the data parameters in the same order that the old {{infobox}} did, so -- that references etc. will display in the expected places. Parameters that -- depend on another parameter are only processed if that parameter is present, -- to avoid phantom references appearing in article reference lists. local function parseDataParameters() preprocessSingleArg('autoheaders') preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') -- different behaviour for italics if blank or absent args['italic title'] = origArgs['italic title'] preprocessSingleArg('decat') preprocessSingleArg('templatestyles') preprocessSingleArg('child templatestyles') preprocessSingleArg('grandchild templatestyles') end -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. function p.infobox(frame) if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end parseDataParameters() return _infobox() end -- For calling via #invoke within a template function p.infoboxTemplate(frame) origArgs = {} for k,v in pairs(frame.args) do origArgs[k] = mw.text.trim(v) end parseDataParameters() return _infobox() end return p kueb5p6xeoq6x7bxu2zyyl7pmxgesrs 803457 803456 2026-07-29T23:47:08Z SM7 3953 42 revisions imported from [[:en:Module:Infobox]] 803456 Scribunto text/plain local p = {} local args = {} local origArgs = {} local root local empty_row_categories = {} local category_in_empty_row_pattern = '%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]' local has_rows = false local yesno = require("Module:Yesno") local lists = { plainlist_t = { patterns = { '^plainlist$', '%splainlist$', '^plainlist%s', '%splainlist%s' }, found = false, styles = 'Plainlist/styles.css' }, hlist_t = { patterns = { '^hlist$', '%shlist$', '^hlist%s', '%shlist%s' }, found = false, styles = 'Hlist/styles.css' } } local function has_list_class(args_to_check) for _, list in pairs(lists) do if not list.found then for _, arg in pairs(args_to_check) do for _, pattern in ipairs(list.patterns) do if mw.ustring.find(arg or '', pattern) then list.found = true break end end if list.found then break end end end end end local function isUntitledChildBox(sval) return sval and ( sval:match( '^%s*<%s*[Tt][Rr]' ) or sval:match( '^%s*\127[^\127]*UNIQ%-%-templatestyles%-%x+%-QINU[^\127]*\127%s*<%s*[Tt][Rr]' ) ) end local function fixChildBoxes(sval, tt) local function notempty( s ) return s and s:match( '%S' ) end if notempty(sval) then local marker = '<span class=special_infobox_marker>' local s = sval -- start moving templatestyles and categories inside of table rows local slast = '' while slast ~= s do slast = s s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*%]%])', '%2%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(\127[^\127]*UNIQ%-%-templatestyles%-%x+%-QINU[^\127]*\127)', '%2%1') end -- end moving templatestyles and categories inside of table rows s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1') s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker) if s:match(marker) then s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '') s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1') s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1') s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1') s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1') end if s:match(marker) then local subcells = mw.text.split(s, marker) s = '' for k = 1, #subcells do if k == 1 then s = s .. subcells[k] .. '</' .. tt .. '></tr>' elseif k == #subcells then local rowstyle = ' style="display:none"' if notempty(subcells[k]) then rowstyle = '' end s = s .. '<tr' .. rowstyle ..'><' .. tt .. ' colspan=2>\n' .. subcells[k] elseif notempty(subcells[k]) then if (k % 2) == 0 then s = s .. subcells[k] else s = s .. '<tr><' .. tt .. ' colspan=2>\n' .. subcells[k] .. '</' .. tt .. '></tr>' end end end end -- the next two lines add a newline at the end of lists for the PHP parser -- [[Special:Diff/849054481]] -- remove when [[:phab:T191516]] is fixed or OBE s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n') s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1') s = mw.ustring.gsub(s, '^(%{%|)', '\n%1') return s else return sval end end -- Cleans empty tables local function cleanInfobox() root = tostring(root) if has_rows == false then root = mw.ustring.gsub(root, '<table[^<>]*>%s*</table>', '') end end -- Returns the union of the values of two tables, as a sequence. local function union(t1, t2) local vals = {} for k, v in pairs(t1) do vals[v] = true end for k, v in pairs(t2) do vals[v] = true end local ret = {} for k, v in pairs(vals) do table.insert(ret, k) end return ret end -- Returns a table containing the numbers of the arguments that exist -- for the specified prefix. For example, if the prefix was 'data', and -- 'data1', 'data2', and 'data5' exist, it would return {1, 2, 5}. local function getArgNums(prefix) local nums = {} for k, v in pairs(args) do local num = tostring(k):match('^' .. prefix .. '([1-9]%d*)$') if num then table.insert(nums, tonumber(num)) end end table.sort(nums) return nums end -- Adds a row to the infobox, with either a header cell -- or a label/data cell combination. local function addRow(rowArgs) if rowArgs.header and rowArgs.header ~= '_BLANK_' then has_rows = true has_list_class({ rowArgs.rowclass, rowArgs.class, args.headerclass }) root :tag('tr') :addClass(rowArgs.rowclass) :addClass( isUntitledChildBox( rowArgs.header ) and 'infobox-hiddenrow' or nil ) :cssText(rowArgs.rowstyle) :tag('th') :attr('colspan', '2') :addClass('infobox-header') :addClass(rowArgs.class) :addClass(args.headerclass) -- @deprecated next; target .infobox-<name> .infobox-header :cssText(args.headerstyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.header, 'th')) if rowArgs.data and not yesno(args.decat) then root:wikitext( '[[Category:Pages using infobox templates with ignored data cells]]' ) end elseif rowArgs.data and rowArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true has_list_class({ rowArgs.rowclass, rowArgs.class }) local row = root:tag('tr') row:addClass(rowArgs.rowclass) row:cssText(rowArgs.rowstyle) if rowArgs.label then row :tag('th') :attr('scope', 'row') :addClass('infobox-label') -- @deprecated next; target .infobox-<name> .infobox-label :cssText(args.labelstyle) :cssText(rowArgs.rowcellstyle) :wikitext(rowArgs.label) :done() else row:addClass( isUntitledChildBox( rowArgs.data ) and 'infobox-hiddenrow' or nil ) end local dataCell = row:tag('td') dataCell :attr('colspan', not rowArgs.label and '2' or nil) :addClass(not rowArgs.label and 'infobox-full-data' or 'infobox-data') :addClass(rowArgs.class) -- @deprecated next; target .infobox-<name> .infobox(-full)-data :cssText(rowArgs.datastyle) :cssText(rowArgs.rowcellstyle) :wikitext(fixChildBoxes(rowArgs.data, 'td')) else table.insert(empty_row_categories, rowArgs.data or '') end end local function renderTitle() if not args.title then return end has_rows = true has_list_class({args.titleclass}) root :tag('caption') :addClass('infobox-title') :addClass(args.titleclass) -- @deprecated next; target .infobox-<name> .infobox-title :cssText(args.titlestyle) :wikitext(args.title) end local function renderAboveRow() if not args.above then return end has_rows = true has_list_class({ args.aboveclass }) root :tag('tr') :addClass( isUntitledChildBox( args.above ) and 'infobox-hiddenrow' or nil ) :tag('th') :attr('colspan', '2') :addClass('infobox-above') :addClass(args.aboveclass) -- @deprecated next; target .infobox-<name> .infobox-above :cssText(args.abovestyle) :wikitext(fixChildBoxes(args.above,'th')) end local function renderBelowRow() if not args.below then return end has_rows = true has_list_class({ args.belowclass }) root :tag('tr') :addClass( isUntitledChildBox( args.below ) and 'infobox-hiddenrow' or nil ) :tag('td') :attr('colspan', '2') :addClass('infobox-below') :addClass(args.belowclass) -- @deprecated next; target .infobox-<name> .infobox-below :cssText(args.belowstyle) :wikitext(fixChildBoxes(args.below,'td')) end local function addSubheaderRow(subheaderArgs) if subheaderArgs.data and subheaderArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true has_list_class({ subheaderArgs.rowclass, subheaderArgs.class }) local row = root:tag('tr') row:addClass(subheaderArgs.rowclass) row:addClass( isUntitledChildBox( subheaderArgs.data ) and 'infobox-hiddenrow' or nil ) local dataCell = row:tag('td') dataCell :attr('colspan', '2') :addClass('infobox-subheader') :addClass(subheaderArgs.class) :cssText(subheaderArgs.datastyle) :cssText(subheaderArgs.rowcellstyle) :wikitext(fixChildBoxes(subheaderArgs.data, 'td')) else table.insert(empty_row_categories, subheaderArgs.data or '') end end local function renderSubheaders() if args.subheader then args.subheader1 = args.subheader end if args.subheaderrowclass then args.subheaderrowclass1 = args.subheaderrowclass end local subheadernums = getArgNums('subheader') for k, num in ipairs(subheadernums) do addSubheaderRow({ data = args['subheader' .. tostring(num)], -- @deprecated next; target .infobox-<name> .infobox-subheader datastyle = args.subheaderstyle, rowcellstyle = args['subheaderstyle' .. tostring(num)], class = args.subheaderclass, rowclass = args['subheaderrowclass' .. tostring(num)] }) end end local function addImageRow(imageArgs) if imageArgs.data and imageArgs.data:gsub(category_in_empty_row_pattern, ''):match('^%S') then has_rows = true has_list_class({ imageArgs.rowclass, imageArgs.class }) local row = root:tag('tr') row:addClass(imageArgs.rowclass) row:addClass( isUntitledChildBox( imageArgs.data ) and 'infobox-hiddenrow' or nil ) local dataCell = row:tag('td') dataCell :attr('colspan', '2') :addClass('infobox-image') :addClass(imageArgs.class) :cssText(imageArgs.datastyle) :wikitext(fixChildBoxes(imageArgs.data, 'td')) else table.insert(empty_row_categories, imageArgs.data or '') end end local function renderImages() if args.image then args.image1 = args.image end if args.caption then args.caption1 = args.caption end local imagenums = getArgNums('image') for k, num in ipairs(imagenums) do local caption = args['caption' .. tostring(num)] local data = mw.html.create():wikitext(args['image' .. tostring(num)]) if caption then data :tag('div') :addClass('infobox-caption') -- @deprecated next; target .infobox-<name> .infobox-caption :cssText(args.captionstyle) :wikitext(caption) end addImageRow({ data = tostring(data), -- @deprecated next; target .infobox-<name> .infobox-image datastyle = args.imagestyle, class = args.imageclass, rowclass = args['imagerowclass' .. tostring(num)] }) end end -- When autoheaders are turned on, preprocesses the rows local function preprocessRows() if not args.autoheaders then return end local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) local lastheader for k, num in ipairs(rownums) do if args['header' .. tostring(num)] then if lastheader then args['header' .. tostring(lastheader)] = nil end lastheader = num elseif args['data' .. tostring(num)] and args['data' .. tostring(num)]:gsub( category_in_empty_row_pattern, '' ):match('^%S') then local data = args['data' .. tostring(num)] if data:gsub(category_in_empty_row_pattern, ''):match('%S') then lastheader = nil end end end if lastheader then args['header' .. tostring(lastheader)] = nil end end -- Gets the union of the header and data argument numbers, -- and renders them all in order local function renderRows() local rownums = union(getArgNums('header'), getArgNums('data')) table.sort(rownums) for k, num in ipairs(rownums) do addRow({ header = args['header' .. tostring(num)], label = args['label' .. tostring(num)], data = args['data' .. tostring(num)], datastyle = args.datastyle, class = args['class' .. tostring(num)], rowclass = args['rowclass' .. tostring(num)], -- @deprecated next; target .infobox-<name> rowclass rowstyle = args['rowstyle' .. tostring(num)], rowcellstyle = args['rowcellstyle' .. tostring(num)] }) end end local function renderNavBar() if not args.name then return end has_rows = true root :tag('tr') :tag('td') :attr('colspan', '2') :addClass('infobox-navbar') :wikitext(require('Module:Navbar')._navbar{ args.name, mini = 1, }) end local function renderItalicTitle() local italicTitle = args['italic title'] and mw.ustring.lower(args['italic title']) if italicTitle == '' or italicTitle == 'force' or italicTitle == 'yes' then root:wikitext(require('Module:Italic title')._main({})) end end -- Categories in otherwise empty rows are collected in empty_row_categories. -- This function adds them to the module output. It is not affected by -- args.decat because this module should not prevent module-external categories -- from rendering. local function renderEmptyRowCategories() for _, s in ipairs(empty_row_categories) do root:wikitext(s) end end -- Render tracking categories. args.decat == turns off tracking categories. local function renderTrackingCategories() if yesno(args.decat) then return end if args.child == 'yes' then if args.title then root:wikitext( '[[Category:Pages using embedded infobox templates with the title parameter]]' ) end elseif #(getArgNums('data')) == 0 and mw.title.getCurrentTitle().namespace == 0 then root:wikitext('[[Category:Articles using infobox templates with no data rows]]') end end --[=[ Loads the templatestyles for the infobox. TODO: FINISH loading base templatestyles here rather than in MediaWiki:Common.css. There are 4-5000 pages with 'raw' infobox tables. See [[Mediawiki_talk:Common.css/to_do#Infobox]] and/or come help :). When we do this we should clean up the inline CSS below too. Will have to do some bizarre conversion category like with sidebar. ]=] local function loadTemplateStyles() local frame = mw.getCurrentFrame() local hlist_templatestyles = '' if lists.hlist_t.found then hlist_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = lists.hlist_t.styles } } end local plainlist_templatestyles = '' if lists.plainlist_t.found then plainlist_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = lists.plainlist_t.styles } } end -- See function description local base_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = 'Module:Infobox/styles.css' } } local templatestyles = '' if args['templatestyles'] then templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['templatestyles'] } } end local child_templatestyles = '' if args['child templatestyles'] then child_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['child templatestyles'] } } end local grandchild_templatestyles = '' if args['grandchild templatestyles'] then grandchild_templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = args['grandchild templatestyles'] } } end return table.concat({ -- hlist -> plainlist -> base is best-effort to preserve old Common.css ordering. -- this ordering is not a guarantee because the rows of interest invoking -- each class may not be on a specific page hlist_templatestyles, plainlist_templatestyles, base_templatestyles, templatestyles, child_templatestyles, grandchild_templatestyles }) end -- common functions between the child and non child cases local function structure_infobox_common() renderSubheaders() renderImages() preprocessRows() renderRows() renderBelowRow() renderNavBar() renderItalicTitle() renderEmptyRowCategories() renderTrackingCategories() cleanInfobox() end -- Specify the overall layout of the infobox, with special settings if the -- infobox is used as a 'child' inside another infobox. local function _infobox() if args.child ~= 'yes' then root = mw.html.create('table') root :addClass(args.subbox == 'yes' and 'infobox-subbox' or 'infobox') :addClass(args.bodyclass) -- @deprecated next; target .infobox-<name> :cssText(args.bodystyle) has_list_class({ args.bodyclass }) renderTitle() renderAboveRow() else root = mw.html.create() root :wikitext(args.title) end structure_infobox_common() return loadTemplateStyles() .. root end -- If the argument exists and isn't blank, add it to the argument table. -- Blank arguments are treated as nil to match the behaviour of ParserFunctions. local function preprocessSingleArg(argName) if origArgs[argName] and origArgs[argName] ~= '' then args[argName] = origArgs[argName] end end -- Assign the parameters with the given prefixes to the args table, in order, in -- batches of the step size specified. This is to prevent references etc. from -- appearing in the wrong order. The prefixTable should be an array containing -- tables, each of which has two possible fields, a "prefix" string and a -- "depend" table. The function always parses parameters containing the "prefix" -- string, but only parses parameters in the "depend" table if the prefix -- parameter is present and non-blank. local function preprocessArgs(prefixTable, step) if type(prefixTable) ~= 'table' then error("Non-table value detected for the prefix table", 2) end if type(step) ~= 'number' then error("Invalid step value detected", 2) end -- Get arguments without a number suffix, and check for bad input. for i,v in ipairs(prefixTable) do if type(v) ~= 'table' or type(v.prefix) ~= "string" or (v.depend and type(v.depend) ~= 'table') then error('Invalid input detected to preprocessArgs prefix table', 2) end preprocessSingleArg(v.prefix) -- Only parse the depend parameter if the prefix parameter is present -- and not blank. if args[v.prefix] and v.depend then for j, dependValue in ipairs(v.depend) do if type(dependValue) ~= 'string' then error('Invalid "depend" parameter value detected in preprocessArgs') end preprocessSingleArg(dependValue) end end end -- Get arguments with number suffixes. local a = 1 -- Counter variable. local moreArgumentsExist = true while moreArgumentsExist == true do moreArgumentsExist = false for i = a, a + step - 1 do for j,v in ipairs(prefixTable) do local prefixArgName = v.prefix .. tostring(i) if origArgs[prefixArgName] then -- Do another loop if any arguments are found, even blank ones. moreArgumentsExist = true preprocessSingleArg(prefixArgName) end -- Process the depend table if the prefix argument is present -- and not blank, or we are processing "prefix1" and "prefix" is -- present and not blank, and if the depend table is present. if v.depend and (args[prefixArgName] or (i == 1 and args[v.prefix])) then for j,dependValue in ipairs(v.depend) do local dependArgName = dependValue .. tostring(i) preprocessSingleArg(dependArgName) end end end end a = a + step end end -- Parse the data parameters in the same order that the old {{infobox}} did, so -- that references etc. will display in the expected places. Parameters that -- depend on another parameter are only processed if that parameter is present, -- to avoid phantom references appearing in article reference lists. local function parseDataParameters() preprocessSingleArg('autoheaders') preprocessSingleArg('child') preprocessSingleArg('bodyclass') preprocessSingleArg('subbox') preprocessSingleArg('bodystyle') preprocessSingleArg('title') preprocessSingleArg('titleclass') preprocessSingleArg('titlestyle') preprocessSingleArg('above') preprocessSingleArg('aboveclass') preprocessSingleArg('abovestyle') preprocessArgs({ {prefix = 'subheader', depend = {'subheaderstyle', 'subheaderrowclass'}} }, 10) preprocessSingleArg('subheaderstyle') preprocessSingleArg('subheaderclass') preprocessArgs({ {prefix = 'image', depend = {'caption', 'imagerowclass'}} }, 10) preprocessSingleArg('captionstyle') preprocessSingleArg('imagestyle') preprocessSingleArg('imageclass') preprocessArgs({ {prefix = 'header'}, {prefix = 'data', depend = {'label'}}, {prefix = 'rowclass'}, {prefix = 'rowstyle'}, {prefix = 'rowcellstyle'}, {prefix = 'class'} }, 50) preprocessSingleArg('headerclass') preprocessSingleArg('headerstyle') preprocessSingleArg('labelstyle') preprocessSingleArg('datastyle') preprocessSingleArg('below') preprocessSingleArg('belowclass') preprocessSingleArg('belowstyle') preprocessSingleArg('name') -- different behaviour for italics if blank or absent args['italic title'] = origArgs['italic title'] preprocessSingleArg('decat') preprocessSingleArg('templatestyles') preprocessSingleArg('child templatestyles') preprocessSingleArg('grandchild templatestyles') end -- If called via #invoke, use the args passed into the invoking template. -- Otherwise, for testing purposes, assume args are being passed directly in. function p.infobox(frame) if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args else origArgs = frame end parseDataParameters() return _infobox() end -- For calling via #invoke within a template function p.infoboxTemplate(frame) origArgs = {} for k,v in pairs(frame.args) do origArgs[k] = mw.text.trim(v) end parseDataParameters() return _infobox() end return p kueb5p6xeoq6x7bxu2zyyl7pmxgesrs टेम्पलेट:Infobox company 10 28448 803459 783217 2025-05-02T17:16:49Z en>Rich Farmbrough 0 Add incorporated field 803459 wikitext text/x-wiki {{Infobox | bodyclass = ib-company vcard | templatestyles = Infobox company/styles.css | child = {{lc:{{{embed}}}}} | decat = yes | titleclass = fn org | titlestyle = font-size: 125%; | title = {{#ifeq:{{lc:{{{embed}}}}} | yes | '''Company''' | {{#if:{{{name|}}} | {{{name}}} | {{#if:{{{company_name|}}}|{{{company_name}}}|<includeonly>{{PAGENAMEBASE}}</includeonly>}} }} }} | imageclass = ib-company-logo logo | image = {{#invoke:InfoboxImage |InfoboxImage |image={{#ifeq:{{lc:{{{embed}}}}} | yes | {{{logo|{{{company_logo|}}}}}} |{{#invoke:WikidataIB |getValue |rank=best |P154 |name=logo |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=no |noicon=yes |maxvals=1 |{{{logo|{{{company_logo|}}}}}} }} }} |size={{{logo_size|}}} |sizedefault=frameless |upright={{{logo_upright|1}}} |alt={{{logo_alt|{{{alt|}}}}}} |class= {{{logo_class|}}} }} | caption = {{{logo_caption|}}} | image2 = {{#invoke:InfoboxImage |InfoboxImage |image={{{image|}}} |size={{{image_size|}}} |sizedefault=frameless |upright={{{image_upright|1}}} |alt={{{image_alt|}}} }} | caption2 = {{{image_caption|}}} | label1 = {{longitem|[[Trade name]]}} | data1 = {{{trade_name|{{{trading_name|}}}}}} | label2 = {{longitem|Native name}} | data2 = {{#if:{{{native_name|}}} | {{#if:{{{native_name_lang|}}} | {{lang|{{{native_name_lang}}}|{{{native_name}}} }} | {{{native_name}}} }} }} | label3 = {{longitem|[[Romanization|Romanized]] name}} | data3 = {{{romanized_name|}}} | label4 = Formerly | class4 = nickname | data4 = {{{former_names|{{{former_name|}}}}}} | label5 = Company type | class5 = category | data5 = {{{type|{{{company_type|}}}}}} | label6 = {{longitem|[[Ticker symbol|Traded as]]}} | data6 = {{{traded_as|}}} | label7 = [[International Securities Identification Number|ISIN]] | data7 = {{#invoke:WikidataIB |getValue |rank=best |P946 |name=ISIN |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{ISIN|}}} }} | label8 = Industry | class8 = category | data8 = {{#invoke:WikidataIB |getValue |rank=best |P452 |name=industry |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{industry|}}} }} | label9 = Genre | class9 = category | data9 = {{{genre|}}} | label10 = Predecessor{{#if:{{{predecessors|}}}|s}} | data10 = {{#invoke:WikidataIB |getValue |rank=best |P155 |name=predecessor |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{predecessors|{{{predecessor|}}}}}} }} | label11 = Incorporated | data11 = {{#invoke:WikidataIB |getValue |rank=best |P1001 |name=incorporated |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{incorporated|{{{incorporation|}}}}}} }} | label12 = Founded | data12 = {{#invoke:WikidataIB |getValue |rank=best |P571 |name=founded |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{founded|{{{foundation|}}}}}} }} | label13 = Founder{{#if:{{{founders|}}}|s}} | class13 = agent | data13 = {{#invoke:WikidataIB |getValue |rank=best |P112 |name=founder |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{founders|{{{founder|}}}}}} }} | label14 = Defunct | data14 = {{#invoke:WikidataIB |getValue |rank=best |P576 |name=defunct |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{defunct|{{{dissolved|}}}}}} }} | label15 = Fate | data15 = {{{fate|}}} | label16 = Successor{{#if:{{{successors|}}}|s}} | data16 = {{#invoke:WikidataIB |getValue |rank=best |P156 |name=successor |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{successors|{{{successor|}}}}}} }} | label17 = Headquarters | class17 = {{#if:{{{hq_location|{{{location|}}}}}} | label | adr}} | data17 = {{#if:{{{hq_location|{{{location|}}}}}}{{{hq_location_city|{{{location_city|}}}}}}{{{hq_location_country|{{{location_country|}}}}}} | {{Comma separated entries | 1 = {{{hq_location|{{{location|}}}}}} | 2 = {{#if:{{{hq_location_city|}}}{{{location_city|}}} |<div class="ib-company-locality locality">{{#if:{{{hq_location_city|}}}{{{location_city|}}} | {{{hq_location_city|{{{location_city|}}}}}} | }}</div>}} | 3 = {{#if:{{{hq_location_country|}}}{{{location_country|}}} |<div class="ib-company-country country-name">{{#if:{{{hq_location_country|}}}{{{location_country|}}} | {{{hq_location_country|{{{location_country|}}}}}} | }}</div>}} }} | {{#if:{{#invoke:WikidataIB |getValue |rank=best |P159 |name=hq_location_city |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }}{{#invoke:WikidataIB |getValue |rank=best |P17 |name=hq_location_country |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }} | {{Comma separated entries | 1 = {{#if:{{#invoke:WikidataIB |getValue |rank=best |P159 |name=hq_location_city |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }} |<div class="ib-company-locality locality">{{#invoke:WikidataIB |getValue |rank=best |P159 |name=headquarters location |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }}</div>}} | 2 = {{#if:{{#invoke:WikidataIB |getValue |rank=best |P17 |name=countrry |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }} |<div class="ib-company-country country-name">{{#invoke:WikidataIB |getValue |rank=best |P17 |name=hq_location_country |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }}</div>}} }} }} }} | label19 = {{longitem|Number of locations}} | data19 = {{#if:{{{num_locations|{{{locations|}}}}}} | {{{num_locations|{{{locations|}}}}}}{{#if:{{{num_locations_year|}}} | &nbsp;({{{num_locations_year}}}) }} }} | label20 = {{longitem|Area{{#if:{{{areas_served|}}}|s}} served}} | data20 = {{{areas_served|{{{area_served|}}}}}} | label21 = {{longitem|Key people}} | class21 = agent | data21 = {{{key_people|}}} | label22 = Products | data22 = {{{products|}}} | label23 = {{longitem|Production output}} | data23 = {{#if:{{{production|}}} | {{{production|}}}{{#if:{{{production_year|}}} | &nbsp;({{{production_year}}}) }} }} | label24 = Brands | data24 = {{{brands|}}} | label25 = Services | class25 = category | data25 = {{{services|}}} | label26 = Revenue | data26 = {{#invoke:WikidataIB |getValue |rank=best |P2139 |name=revenue |qual=P585 |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 |{{{revenue|}}}{{#if:{{{revenue_year|}}} | &nbsp;({{{revenue_year}}}) }} }} | label27 = {{longitem|[[Earnings before interest and taxes|Operating income]]}} | data27 = {{#invoke:WikidataIB |getValue |rank=best |P3362 |name=operating_income |qual=P585 |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 |{{{operating_income|}}}{{#if:{{{income_year|}}} | &nbsp;({{{income_year}}}) }} }} | label28 = {{longitem|[[Net income]]}} | data28 = {{#invoke:WikidataIB |getValue |rank=best |P2295 |name=net_income |qual=P585 |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 |{{{net_income|{{{profit|}}}}}}{{#if:{{{net_income_year|{{{profit_year|}}}}}} | &nbsp;({{{net_income_year|{{{profit_year|}}}}}}) }} }} | label29 = [[Assets under management|AUM]] | data29 = {{{aum|}}} | label30 = {{nowrap|[[Asset|Total assets]]}} | data30 = {{#invoke:WikidataIB |getValue |rank=best |P2403 |name=assets |qual=P585 |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 |{{{assets|}}}{{#if:{{{assets_year|}}} | &nbsp;({{{assets_year}}}) }} }} | label31 = {{nowrap|[[Equity (finance)|Total equity]]}} | data31 = {{#if:{{{equity|}}} | {{{equity}}}{{#if:{{{equity_year|}}} | &nbsp;({{{equity_year}}})}} }} | label32 = Owner{{#if:{{{owners|}}}|s}} | data32 = {{{owners|{{{owner|}}}}}} | label33 = Members | data33 = {{#if:{{{members|}}} | {{{members}}}{{#if:{{{members_year|}}} | &nbsp;({{{members_year}}})}} }} | label34 = {{longitem|Number of employees}} | data34 = {{#invoke:WikidataIB |getValue |rank=best |P1128 |name=num_employees |qual=P585 |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 |{{{num_employees|}}}{{#if:{{{num_employees_year|}}} | &nbsp;({{{num_employees_year}}}) }} }} | label35 = [[Parent company|Parent]] | data35 = {{#ifeq:{{{owners|}}}{{{owner|}}}{{{parent|}}} || {{#invoke:WikidataIB |getValue |rank=best |P749 |name=parent |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes }} | {{{parent|}}} }} | label36 = [[Division (business)|Divisions]] | data36 = {{{divisions|}}} | label37 = [[Subsidiary|Subsidiaries]] | data37 = {{{subsid|}}} | data39 = {{{module|}}} | label40 = [[Basel III|Capital ratio]] | data40 = {{{ratio|}}} | label41 = Rating | data41 = {{{rating|}}} | label42 = Website | data42 = {{#if:{{{website|{{{homepage|}}}}}} |{{{website|{{{homepage|}}}}}} |{{#invoke:WikidataIB |url2 |url={{#invoke:WikidataIB |getValue |rank=best |P856 |name=website |qid={{{qid|}}} |suppressfields={{{suppressfields|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |onlysourced=yes |maxvals=1}} }} }} | below = {{#if:{{{footnotes|}}} | '''Footnotes{{\}}references'''{{break}}{{{footnotes}}} }} }}<!-- Tracking categories: -->{{main other|{{#if:{{{trading_name|}}}|[[Category:Pages using infobox company using trading name]] }}{{#ifeq:{{{logo|{{{company_logo|{{wikidata|property|raw|P154}}}}}}}}|{{{logo|{{{company_logo|}}}}}}||[[Category:Pages using infobox company with a logo from wikidata]] }}{{#if:{{{image|}}}|{{#ifeq:{{#invoke:string|replace|{{{image|}}}| |_}}|{{#invoke:string|replace|{{{logo|{{{company_logo|{{wikidata|property|raw|P154}}}}}}}}| |_}}|[[Category:Pages using infobox company with a duplicate image]]|}}|}} }}<!-- -->{{#invoke:Check for clobbered parameters|check|nested=1|template=Infobox company|cat={{main other|Category:Pages using infobox company with conflicting parameters}}|name; company_name|logo; company_logo|logo_alt; alt|trade_name; trading_name|former_names; former_name|type; company_type|predecessors; predecessor|successors; successor|foundation; founded|founders; founder|defunct; dissolved|hq_location; location|hq_location_city; location_city|hq_location_country; location_country|num_locations; locations|areas_served; area_served|net_income; profit|net_income_year; profit_year|owners; owner |homepage; website }}{{#invoke:Check for unknown parameters|check|unknown={{main other|[[Category:Pages using infobox company with unknown parameters|_VALUE_{{PAGENAME}}]]}}|preview=Page using [[Template:Infobox company]] with unknown parameter "_VALUE_" | ignoreblank=y | alt | area_served | areas_served | assets | assets_year | aum | brands | company_logo | company_name | company_type | defunct | dissolved | divisions | embed | equity | equity_year | fate | footnotes | former_name | former_names | foundation | founded | founder | founders | genre | homepage | hq_location | hq_location_city | hq_location_country | incorporated | image | image_alt | image_caption | image_size | image_upright | income_year | industry | ISIN | key_people | location | location_city | location_country | locations | logo | logo_alt | logo_caption | logo_class | logo_size | logo_upright | members | members_year | module | name | native_name | native_name_lang | net_income | net_income_year | num_employees | num_employees_year | num_locations | num_locations_year | operating_income | owner | owners | parent | predecessor | predecessors | production | production_year | products | profit | profit_year | rating | ratio | revenue | revenue_year | romanized_name | services | subsid | successor | successors | traded_as | trade_name | trading_name | type | website| qid | fetchwikidata | suppressfields | noicon | nocat | demo | categories }}<noinclude> {{documentation}} </noinclude> g6uuqljm2wzl9ehj86kj5xe4bhlt1ul 803460 803459 2025-09-05T00:14:20Z en>Zackmann08 0 /* top */ adding support for headquarters which is consistently being used. 803460 wikitext text/x-wiki {{Infobox | bodyclass = ib-company vcard | templatestyles = Infobox company/styles.css | child = {{lc:{{{embed}}}}} | decat = yes | titleclass = fn org | titlestyle = font-size: 125%; | title = {{#ifeq:{{lc:{{{embed}}}}} | yes | '''Company''' | {{#if:{{{name|}}} | {{{name}}} | {{#if:{{{company_name|}}}|{{{company_name}}}|<includeonly>{{PAGENAMEBASE}}</includeonly>}} }} }} | imageclass = ib-company-logo logo | image = {{#invoke:InfoboxImage |InfoboxImage |image={{#ifeq:{{lc:{{{embed}}}}} | yes | {{{logo|{{{company_logo|}}}}}} |{{#invoke:WikidataIB |getValue |rank=best |P154 |name=logo |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=no |noicon=yes |maxvals=1 |{{{logo|{{{company_logo|}}}}}} }} }} |size={{{logo_size|}}} |sizedefault=frameless |upright={{{logo_upright|1}}} |alt={{{logo_alt|{{{alt|}}}}}} |class= {{{logo_class|}}} }} | caption = {{{logo_caption|}}} | image2 = {{#invoke:InfoboxImage |InfoboxImage |image={{{image|}}} |size={{{image_size|}}} |sizedefault=frameless |upright={{{image_upright|1}}} |alt={{{image_alt|}}} }} | caption2 = {{{image_caption|}}} | label1 = {{longitem|[[Trade name]]}} | data1 = {{{trade_name|{{{trading_name|}}}}}} | label2 = {{longitem|Native name}} | data2 = {{#if:{{{native_name|}}} | {{#if:{{{native_name_lang|}}} | {{lang|{{{native_name_lang}}}|{{{native_name}}} }} | {{{native_name}}} }} }} | label3 = {{longitem|[[Romanization|Romanized]] name}} | data3 = {{{romanized_name|}}} | label4 = Formerly | class4 = nickname | data4 = {{{former_names|{{{former_name|}}}}}} | label5 = Company type | class5 = category | data5 = {{{type|{{{company_type|}}}}}} | label6 = {{longitem|[[Ticker symbol|Traded as]]}} | data6 = {{{traded_as|}}} | label7 = [[International Securities Identification Number|ISIN]] | data7 = {{#invoke:WikidataIB |getValue |rank=best |P946 |name=ISIN |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{ISIN|}}} }} | label8 = Industry | class8 = category | data8 = {{#invoke:WikidataIB |getValue |rank=best |P452 |name=industry |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{industry|}}} }} | label9 = Genre | class9 = category | data9 = {{{genre|}}} | label10 = Predecessor{{#if:{{{predecessors|}}}|s}} | data10 = {{#invoke:WikidataIB |getValue |rank=best |P155 |name=predecessor |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{predecessors|{{{predecessor|}}}}}} }} | label11 = Incorporated | data11 = {{#invoke:WikidataIB |getValue |rank=best |P1001 |name=incorporated |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{incorporated|{{{incorporation|}}}}}} }} | label12 = Founded | data12 = {{#invoke:WikidataIB |getValue |rank=best |P571 |name=founded |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{founded|{{{foundation|}}}}}} }} | label13 = Founder{{#if:{{{founders|}}}|s}} | class13 = agent | data13 = {{#invoke:WikidataIB |getValue |rank=best |P112 |name=founder |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{founders|{{{founder|}}}}}} }} | label14 = Defunct | data14 = {{#invoke:WikidataIB |getValue |rank=best |P576 |name=defunct |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{defunct|{{{dissolved|}}}}}} }} | label15 = Fate | data15 = {{{fate|}}} | label16 = Successor{{#if:{{{successors|}}}|s}} | data16 = {{#invoke:WikidataIB |getValue |rank=best |P156 |name=successor |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{successors|{{{successor|}}}}}} }} | label17 = Headquarters | class17 = {{#if:{{{hq_location|{{{location|{{{headquarters|}}}}}}}}} | label | adr}} | data17 = {{#if:{{{hq_location|{{{location|{{{headquarters|}}}}}}}}}{{{hq_location_city|{{{location_city|}}}}}}{{{hq_location_country|{{{location_country|}}}}}} | {{Comma separated entries | 1 = {{{hq_location|{{{location|}}}}}} | 2 = {{#if:{{{hq_location_city|{{{headquarters|}}}}}}{{{location_city|}}} |<div class="ib-company-locality locality">{{#if:{{{hq_location_city|}}}{{{location_city|}}} | {{{hq_location_city|{{{location_city|}}}}}} | }}</div>}} | 3 = {{#if:{{{hq_location_country|}}}{{{location_country|}}} |<div class="ib-company-country country-name">{{#if:{{{hq_location_country|}}}{{{location_country|}}} | {{{hq_location_country|{{{location_country|}}}}}} | }}</div>}} }} | {{#if:{{#invoke:WikidataIB |getValue |rank=best |P159 |name=hq_location_city |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }}{{#invoke:WikidataIB |getValue |rank=best |P17 |name=hq_location_country |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }} | {{Comma separated entries | 1 = {{#if:{{#invoke:WikidataIB |getValue |rank=best |P159 |name=hq_location_city |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }} |<div class="ib-company-locality locality">{{#invoke:WikidataIB |getValue |rank=best |P159 |name=headquarters location |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }}</div>}} | 2 = {{#if:{{#invoke:WikidataIB |getValue |rank=best |P17 |name=countrry |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }} |<div class="ib-company-country country-name">{{#invoke:WikidataIB |getValue |rank=best |P17 |name=hq_location_country |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }}</div>}} }} }} }} | label19 = {{longitem|Number of locations}} | data19 = {{#if:{{{num_locations|{{{locations|}}}}}} | {{{num_locations|{{{locations|}}}}}}{{#if:{{{num_locations_year|}}} | &nbsp;({{{num_locations_year}}}) }} }} | label20 = {{longitem|Area{{#if:{{{areas_served|}}}|s}} served}} | data20 = {{{areas_served|{{{area_served|}}}}}} | label21 = {{longitem|Key people}} | class21 = agent | data21 = {{{key_people|}}} | label22 = Products | data22 = {{{products|}}} | label23 = {{longitem|Production output}} | data23 = {{#if:{{{production|}}} | {{{production|}}}{{#if:{{{production_year|}}} | &nbsp;({{{production_year}}}) }} }} | label24 = Brands | data24 = {{{brands|}}} | label25 = Services | class25 = category | data25 = {{{services|}}} | label26 = Revenue | data26 = {{#invoke:WikidataIB |getValue |rank=best |P2139 |name=revenue |qual=P585 |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 |{{{revenue|}}}{{#if:{{{revenue_year|}}} | &nbsp;({{{revenue_year}}}) }} }} | label27 = {{longitem|[[Earnings before interest and taxes|Operating income]]}} | data27 = {{#invoke:WikidataIB |getValue |rank=best |P3362 |name=operating_income |qual=P585 |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 |{{{operating_income|}}}{{#if:{{{income_year|}}} | &nbsp;({{{income_year}}}) }} }} | label28 = {{longitem|[[Net income]]}} | data28 = {{#invoke:WikidataIB |getValue |rank=best |P2295 |name=net_income |qual=P585 |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 |{{{net_income|{{{profit|}}}}}}{{#if:{{{net_income_year|{{{profit_year|}}}}}} | &nbsp;({{{net_income_year|{{{profit_year|}}}}}}) }} }} | label29 = [[Assets under management|AUM]] | data29 = {{{aum|}}} | label30 = {{nowrap|[[Asset|Total assets]]}} | data30 = {{#invoke:WikidataIB |getValue |rank=best |P2403 |name=assets |qual=P585 |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 |{{{assets|}}}{{#if:{{{assets_year|}}} | &nbsp;({{{assets_year}}}) }} }} | label31 = {{nowrap|[[Equity (finance)|Total equity]]}} | data31 = {{#if:{{{equity|}}} | {{{equity}}}{{#if:{{{equity_year|}}} | &nbsp;({{{equity_year}}})}} }} | label32 = Owner{{#if:{{{owners|}}}|s}} | data32 = {{{owners|{{{owner|}}}}}} | label33 = Members | data33 = {{#if:{{{members|}}} | {{{members}}}{{#if:{{{members_year|}}} | &nbsp;({{{members_year}}})}} }} | label34 = {{longitem|Number of employees}} | data34 = {{#invoke:WikidataIB |getValue |rank=best |P1128 |name=num_employees |qual=P585 |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 |{{{num_employees|}}}{{#if:{{{num_employees_year|}}} | &nbsp;({{{num_employees_year}}}) }} }} | label35 = [[Parent company|Parent]] | data35 = {{#ifeq:{{{owners|}}}{{{owner|}}}{{{parent|}}} || {{#invoke:WikidataIB |getValue |rank=best |P749 |name=parent |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes }} | {{{parent|}}} }} | label36 = [[Division (business)|Divisions]] | data36 = {{{divisions|}}} | label37 = [[Subsidiary|Subsidiaries]] | data37 = {{{subsid|}}} | data39 = {{{module|}}} | label40 = [[Basel III|Capital ratio]] | data40 = {{{ratio|}}} | label41 = Rating | data41 = {{{rating|}}} | label42 = Website | data42 = {{#if:{{{website|{{{homepage|}}}}}} |{{{website|{{{homepage|}}}}}} |{{#invoke:WikidataIB |url2 |url={{#invoke:WikidataIB |getValue |rank=best |P856 |name=website |qid={{{qid|}}} |suppressfields={{{suppressfields|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |onlysourced=yes |maxvals=1}} }} }} | below = {{#if:{{{footnotes|}}} | '''Footnotes{{\}}references'''{{break}}{{{footnotes}}} }} }}<!-- Tracking categories: -->{{main other|{{#if:{{{trading_name|}}}|[[Category:Pages using infobox company using trading name]] }}{{#ifeq:{{{logo|{{{company_logo|{{wikidata|property|raw|P154}}}}}}}}|{{{logo|{{{company_logo|}}}}}}||[[Category:Pages using infobox company with a logo from wikidata]] }}{{#if:{{{image|}}}|{{#ifeq:{{#invoke:string|replace|{{{image|}}}| |_}}|{{#invoke:string|replace|{{{logo|{{{company_logo|{{wikidata|property|raw|P154}}}}}}}}| |_}}|[[Category:Pages using infobox company with a duplicate image]]|}}|}} }}<!-- -->{{#invoke:Check for clobbered parameters|check|nested=1|template=Infobox company|cat={{main other|Category:Pages using infobox company with conflicting parameters}}|name; company_name|logo; company_logo|logo_alt; alt|trade_name; trading_name|former_names; former_name|type; company_type|predecessors; predecessor|successors; successor|foundation; founded|founders; founder|defunct; dissolved|hq_location; location|hq_location_city; location_city|hq_location_country; location_country|num_locations; locations|areas_served; area_served|net_income; profit|net_income_year; profit_year|owners; owner |homepage; website }}{{#invoke:Check for unknown parameters|check|unknown={{main other|[[Category:Pages using infobox company with unknown parameters|_VALUE_{{PAGENAME}}]]}}|preview=Page using [[Template:Infobox company]] with unknown parameter "_VALUE_" | ignoreblank=y | alt | area_served | areas_served | assets | assets_year | aum | brands | company_logo | company_name | company_type | defunct | dissolved | divisions | embed | equity | equity_year | fate | footnotes | headquarters | former_name | former_names | foundation | founded | founder | founders | genre | homepage | hq_location | hq_location_city | hq_location_country | incorporated | image | image_alt | image_caption | image_size | image_upright | income_year | industry | ISIN | key_people | location | location_city | location_country | locations | logo | logo_alt | logo_caption | logo_class | logo_size | logo_upright | members | members_year | module | name | native_name | native_name_lang | net_income | net_income_year | num_employees | num_employees_year | num_locations | num_locations_year | operating_income | owner | owners | parent | predecessor | predecessors | production | production_year | products | profit | profit_year | rating | ratio | revenue | revenue_year | romanized_name | services | subsid | successor | successors | traded_as | trade_name | trading_name | type | website| qid | fetchwikidata | suppressfields | noicon | nocat | demo | categories }}<noinclude> {{documentation}} </noinclude> 1tdb4rb7vuefu7w853n2ld2g8uqur0n 803461 803460 2025-09-05T23:38:59Z en>Zackmann08 0 Adding support for subsidiaries Which keeps popping up 803461 wikitext text/x-wiki {{Infobox | bodyclass = ib-company vcard | templatestyles = Infobox company/styles.css | child = {{lc:{{{embed}}}}} | decat = yes | titleclass = fn org | titlestyle = font-size: 125%; | title = {{#ifeq:{{lc:{{{embed}}}}} | yes | '''Company''' | {{#if:{{{name|}}} | {{{name}}} | {{#if:{{{company_name|}}}|{{{company_name}}}|<includeonly>{{PAGENAMEBASE}}</includeonly>}} }} }} | imageclass = ib-company-logo logo | image = {{#invoke:InfoboxImage |InfoboxImage |image={{#ifeq:{{lc:{{{embed}}}}} | yes | {{{logo|{{{company_logo|}}}}}} |{{#invoke:WikidataIB |getValue |rank=best |P154 |name=logo |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=no |noicon=yes |maxvals=1 |{{{logo|{{{company_logo|}}}}}} }} }} |size={{{logo_size|}}} |sizedefault=frameless |upright={{{logo_upright|1}}} |alt={{{logo_alt|{{{alt|}}}}}} |class= {{{logo_class|}}} }} | caption = {{{logo_caption|}}} | image2 = {{#invoke:InfoboxImage |InfoboxImage |image={{{image|}}} |size={{{image_size|}}} |sizedefault=frameless |upright={{{image_upright|1}}} |alt={{{image_alt|}}} }} | caption2 = {{{image_caption|}}} | label1 = {{longitem|[[Trade name]]}} | data1 = {{{trade_name|{{{trading_name|}}}}}} | label2 = {{longitem|Native name}} | data2 = {{#if:{{{native_name|}}} | {{#if:{{{native_name_lang|}}} | {{lang|{{{native_name_lang}}}|{{{native_name}}} }} | {{{native_name}}} }} }} | label3 = {{longitem|[[Romanization|Romanized]] name}} | data3 = {{{romanized_name|}}} | label4 = Formerly | class4 = nickname | data4 = {{{former_names|{{{former_name|}}}}}} | label5 = Company type | class5 = category | data5 = {{{type|{{{company_type|}}}}}} | label6 = {{longitem|[[Ticker symbol|Traded as]]}} | data6 = {{{traded_as|}}} | label7 = [[International Securities Identification Number|ISIN]] | data7 = {{#invoke:WikidataIB |getValue |rank=best |P946 |name=ISIN |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{ISIN|}}} }} | label8 = Industry | class8 = category | data8 = {{#invoke:WikidataIB |getValue |rank=best |P452 |name=industry |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{industry|}}} }} | label9 = Genre | class9 = category | data9 = {{{genre|}}} | label10 = Predecessor{{#if:{{{predecessors|}}}|s}} | data10 = {{#invoke:WikidataIB |getValue |rank=best |P155 |name=predecessor |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{predecessors|{{{predecessor|}}}}}} }} | label11 = Incorporated | data11 = {{#invoke:WikidataIB |getValue |rank=best |P1001 |name=incorporated |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{incorporated|{{{incorporation|}}}}}} }} | label12 = Founded | data12 = {{#invoke:WikidataIB |getValue |rank=best |P571 |name=founded |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{founded|{{{foundation|}}}}}} }} | label13 = Founder{{#if:{{{founders|}}}|s}} | class13 = agent | data13 = {{#invoke:WikidataIB |getValue |rank=best |P112 |name=founder |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{founders|{{{founder|}}}}}} }} | label14 = Defunct | data14 = {{#invoke:WikidataIB |getValue |rank=best |P576 |name=defunct |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{defunct|{{{dissolved|}}}}}} }} | label15 = Fate | data15 = {{{fate|}}} | label16 = Successor{{#if:{{{successors|}}}|s}} | data16 = {{#invoke:WikidataIB |getValue |rank=best |P156 |name=successor |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{successors|{{{successor|}}}}}} }} | label17 = Headquarters | class17 = {{#if:{{{hq_location|{{{location|{{{headquarters|}}}}}}}}} | label | adr}} | data17 = {{#if:{{{hq_location|{{{location|{{{headquarters|}}}}}}}}}{{{hq_location_city|{{{location_city|}}}}}}{{{hq_location_country|{{{location_country|}}}}}} | {{Comma separated entries | 1 = {{{hq_location|{{{location|}}}}}} | 2 = {{#if:{{{hq_location_city|{{{headquarters|}}}}}}{{{location_city|}}} |<div class="ib-company-locality locality">{{#if:{{{hq_location_city|}}}{{{location_city|}}} | {{{hq_location_city|{{{location_city|}}}}}} | }}</div>}} | 3 = {{#if:{{{hq_location_country|}}}{{{location_country|}}} |<div class="ib-company-country country-name">{{#if:{{{hq_location_country|}}}{{{location_country|}}} | {{{hq_location_country|{{{location_country|}}}}}} | }}</div>}} }} | {{#if:{{#invoke:WikidataIB |getValue |rank=best |P159 |name=hq_location_city |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }}{{#invoke:WikidataIB |getValue |rank=best |P17 |name=hq_location_country |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }} | {{Comma separated entries | 1 = {{#if:{{#invoke:WikidataIB |getValue |rank=best |P159 |name=hq_location_city |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }} |<div class="ib-company-locality locality">{{#invoke:WikidataIB |getValue |rank=best |P159 |name=headquarters location |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }}</div>}} | 2 = {{#if:{{#invoke:WikidataIB |getValue |rank=best |P17 |name=countrry |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }} |<div class="ib-company-country country-name">{{#invoke:WikidataIB |getValue |rank=best |P17 |name=hq_location_country |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }}</div>}} }} }} }} | label19 = {{longitem|Number of locations}} | data19 = {{#if:{{{num_locations|{{{locations|}}}}}} | {{{num_locations|{{{locations|}}}}}}{{#if:{{{num_locations_year|}}} | &nbsp;({{{num_locations_year}}}) }} }} | label20 = {{longitem|Area{{#if:{{{areas_served|}}}|s}} served}} | data20 = {{{areas_served|{{{area_served|}}}}}} | label21 = {{longitem|Key people}} | class21 = agent | data21 = {{{key_people|}}} | label22 = Products | data22 = {{{products|}}} | label23 = {{longitem|Production output}} | data23 = {{#if:{{{production|}}} | {{{production|}}}{{#if:{{{production_year|}}} | &nbsp;({{{production_year}}}) }} }} | label24 = Brands | data24 = {{{brands|}}} | label25 = Services | class25 = category | data25 = {{{services|}}} | label26 = Revenue | data26 = {{#invoke:WikidataIB |getValue |rank=best |P2139 |name=revenue |qual=P585 |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 |{{{revenue|}}}{{#if:{{{revenue_year|}}} | &nbsp;({{{revenue_year}}}) }} }} | label27 = {{longitem|[[Earnings before interest and taxes|Operating income]]}} | data27 = {{#invoke:WikidataIB |getValue |rank=best |P3362 |name=operating_income |qual=P585 |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 |{{{operating_income|}}}{{#if:{{{income_year|}}} | &nbsp;({{{income_year}}}) }} }} | label28 = {{longitem|[[Net income]]}} | data28 = {{#invoke:WikidataIB |getValue |rank=best |P2295 |name=net_income |qual=P585 |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 |{{{net_income|{{{profit|}}}}}}{{#if:{{{net_income_year|{{{profit_year|}}}}}} | &nbsp;({{{net_income_year|{{{profit_year|}}}}}}) }} }} | label29 = [[Assets under management|AUM]] | data29 = {{{aum|}}} | label30 = {{nowrap|[[Asset|Total assets]]}} | data30 = {{#invoke:WikidataIB |getValue |rank=best |P2403 |name=assets |qual=P585 |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 |{{{assets|}}}{{#if:{{{assets_year|}}} | &nbsp;({{{assets_year}}}) }} }} | label31 = {{nowrap|[[Equity (finance)|Total equity]]}} | data31 = {{#if:{{{equity|}}} | {{{equity}}}{{#if:{{{equity_year|}}} | &nbsp;({{{equity_year}}})}} }} | label32 = Owner{{#if:{{{owners|}}}|s}} | data32 = {{{owners|{{{owner|}}}}}} | label33 = Members | data33 = {{#if:{{{members|}}} | {{{members}}}{{#if:{{{members_year|}}} | &nbsp;({{{members_year}}})}} }} | label34 = {{longitem|Number of employees}} | data34 = {{#invoke:WikidataIB |getValue |rank=best |P1128 |name=num_employees |qual=P585 |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 |{{{num_employees|}}}{{#if:{{{num_employees_year|}}} | &nbsp;({{{num_employees_year}}}) }} }} | label35 = [[Parent company|Parent]] | data35 = {{#ifeq:{{{owners|}}}{{{owner|}}}{{{parent|}}} || {{#invoke:WikidataIB |getValue |rank=best |P749 |name=parent |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes }} | {{{parent|}}} }} | label36 = [[Division (business)|Divisions]] | data36 = {{{divisions|}}} | label37 = [[Subsidiary|Subsidiaries]] | data37 = {{{subsid|{{{subsidiaries|}}}}}} | data39 = {{{module|}}} | label40 = [[Basel III|Capital ratio]] | data40 = {{{ratio|}}} | label41 = Rating | data41 = {{{rating|}}} | label42 = Website | data42 = {{#if:{{{website|{{{homepage|}}}}}} |{{{website|{{{homepage|}}}}}} |{{#invoke:WikidataIB |url2 |url={{#invoke:WikidataIB |getValue |rank=best |P856 |name=website |qid={{{qid|}}} |suppressfields={{{suppressfields|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |onlysourced=yes |maxvals=1}} }} }} | below = {{#if:{{{footnotes|}}} | '''Footnotes{{\}}references'''{{break}}{{{footnotes}}} }} }}<!-- Tracking categories: -->{{main other|{{#if:{{{trading_name|}}}|[[Category:Pages using infobox company using trading name]] }}{{#ifeq:{{{logo|{{{company_logo|{{wikidata|property|raw|P154}}}}}}}}|{{{logo|{{{company_logo|}}}}}}||[[Category:Pages using infobox company with a logo from wikidata]] }}{{#if:{{{image|}}}|{{#ifeq:{{#invoke:string|replace|{{{image|}}}| |_}}|{{#invoke:string|replace|{{{logo|{{{company_logo|{{wikidata|property|raw|P154}}}}}}}}| |_}}|[[Category:Pages using infobox company with a duplicate image]]|}}|}} }}<!-- -->{{#invoke:Check for clobbered parameters|check|nested=1|template=Infobox company|cat={{main other|Category:Pages using infobox company with conflicting parameters}}|name; company_name|logo; company_logo|logo_alt; alt|trade_name; trading_name|former_names; former_name|type; company_type|predecessors; predecessor|successors; successor|foundation; founded|founders; founder|defunct; dissolved|hq_location; location|hq_location_city; location_city|hq_location_country; location_country|num_locations; locations|areas_served; area_served|net_income; profit|net_income_year; profit_year|owners; owner |homepage; website }}{{#invoke:Check for unknown parameters|check|unknown={{main other|[[Category:Pages using infobox company with unknown parameters|_VALUE_{{PAGENAME}}]]}}|preview=Page using [[Template:Infobox company]] with unknown parameter "_VALUE_" | ignoreblank=y | alt | area_served | areas_served | assets | assets_year | aum | brands | company_logo | company_name | company_type | defunct | dissolved | divisions | embed | equity | equity_year | fate | footnotes | headquarters | former_name | former_names | foundation | founded | founder | founders | genre | homepage | hq_location | hq_location_city | hq_location_country | incorporated | image | image_alt | image_caption | image_size | image_upright | income_year | industry | ISIN | key_people | location | location_city | location_country | locations | logo | logo_alt | logo_caption | logo_class | logo_size | logo_upright | members | members_year | module | name | native_name | native_name_lang | net_income | net_income_year | num_employees | num_employees_year | num_locations | num_locations_year | operating_income | owner | owners | parent | predecessor | predecessors | production | production_year | products | profit | profit_year | rating | ratio | revenue | revenue_year | romanized_name | services | subsid | successor | successors | traded_as | trade_name | trading_name | type | website| qid | fetchwikidata | suppressfields | noicon | nocat | demo | categories }}<noinclude> {{documentation}} </noinclude> fmaz6gykehhkkhubmi3u9qwuzbpl5o5 803462 803461 2025-09-05T23:49:52Z en>Zackmann08 0 /* top */ 803462 wikitext text/x-wiki {{Infobox | bodyclass = ib-company vcard | templatestyles = Infobox company/styles.css | child = {{lc:{{{embed}}}}} | decat = yes | titleclass = fn org | titlestyle = font-size: 125%; | title = {{#ifeq:{{lc:{{{embed}}}}} | yes | '''Company''' | {{#if:{{{name|}}} | {{{name}}} | {{#if:{{{company_name|}}}|{{{company_name}}}|<includeonly>{{PAGENAMEBASE}}</includeonly>}} }} }} | imageclass = ib-company-logo logo | image = {{#invoke:InfoboxImage |InfoboxImage |image={{#ifeq:{{lc:{{{embed}}}}} | yes | {{{logo|{{{company_logo|}}}}}} |{{#invoke:WikidataIB |getValue |rank=best |P154 |name=logo |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=no |noicon=yes |maxvals=1 |{{{logo|{{{company_logo|}}}}}} }} }} |size={{{logo_size|}}} |sizedefault=frameless |upright={{{logo_upright|1}}} |alt={{{logo_alt|{{{alt|}}}}}} |class= {{{logo_class|}}} }} | caption = {{{logo_caption|}}} | image2 = {{#invoke:InfoboxImage |InfoboxImage |image={{{image|}}} |size={{{image_size|}}} |sizedefault=frameless |upright={{{image_upright|1}}} |alt={{{image_alt|}}} }} | caption2 = {{{image_caption|}}} | label1 = {{longitem|[[Trade name]]}} | data1 = {{{trade_name|{{{trading_name|}}}}}} | label2 = {{longitem|Native name}} | data2 = {{#if:{{{native_name|}}} | {{#if:{{{native_name_lang|}}} | {{lang|{{{native_name_lang}}}|{{{native_name}}} }} | {{{native_name}}} }} }} | label3 = {{longitem|[[Romanization|Romanized]] name}} | data3 = {{{romanized_name|}}} | label4 = Formerly | class4 = nickname | data4 = {{{former_names|{{{former_name|}}}}}} | label5 = Company type | class5 = category | data5 = {{{type|{{{company_type|}}}}}} | label6 = {{longitem|[[Ticker symbol|Traded as]]}} | data6 = {{{traded_as|}}} | label7 = [[International Securities Identification Number|ISIN]] | data7 = {{#invoke:WikidataIB |getValue |rank=best |P946 |name=ISIN |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{ISIN|}}} }} | label8 = Industry | class8 = category | data8 = {{#invoke:WikidataIB |getValue |rank=best |P452 |name=industry |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{industry|}}} }} | label9 = Genre | class9 = category | data9 = {{{genre|}}} | label10 = Predecessor{{#if:{{{predecessors|}}}|s}} | data10 = {{#invoke:WikidataIB |getValue |rank=best |P155 |name=predecessor |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{predecessors|{{{predecessor|}}}}}} }} | label11 = Incorporated | data11 = {{#invoke:WikidataIB |getValue |rank=best |P1001 |name=incorporated |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{incorporated|{{{incorporation|}}}}}} }} | label12 = Founded | data12 = {{#invoke:WikidataIB |getValue |rank=best |P571 |name=founded |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{founded|{{{foundation|}}}}}} }} | label13 = Founder{{#if:{{{founders|}}}|s}} | class13 = agent | data13 = {{#invoke:WikidataIB |getValue |rank=best |P112 |name=founder |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{founders|{{{founder|}}}}}} }} | label14 = Defunct | data14 = {{#invoke:WikidataIB |getValue |rank=best |P576 |name=defunct |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{defunct|{{{dissolved|}}}}}} }} | label15 = Fate | data15 = {{{fate|}}} | label16 = Successor{{#if:{{{successors|}}}|s}} | data16 = {{#invoke:WikidataIB |getValue |rank=best |P156 |name=successor |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{successors|{{{successor|}}}}}} }} | label17 = Headquarters | class17 = {{#if:{{{hq_location|{{{location|{{{headquarters|}}}}}}}}} | label | adr}} | data17 = {{#if:{{{hq_location|{{{location|{{{headquarters|}}}}}}}}}{{{hq_location_city|{{{location_city|}}}}}}{{{hq_location_country|{{{location_country|}}}}}} | {{Comma separated entries | 1 = {{{hq_location|{{{location|}}}}}} | 2 = {{#if:{{{hq_location_city|{{{headquarters|}}}}}}{{{location_city|}}} |<div class="ib-company-locality locality">{{#if:{{{hq_location_city|}}}{{{location_city|}}} | {{{hq_location_city|{{{location_city|}}}}}} | }}</div>}} | 3 = {{#if:{{{hq_location_country|}}}{{{location_country|}}} |<div class="ib-company-country country-name">{{#if:{{{hq_location_country|}}}{{{location_country|}}} | {{{hq_location_country|{{{location_country|}}}}}} | }}</div>}} }} | {{#if:{{#invoke:WikidataIB |getValue |rank=best |P159 |name=hq_location_city |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }}{{#invoke:WikidataIB |getValue |rank=best |P17 |name=hq_location_country |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }} | {{Comma separated entries | 1 = {{#if:{{#invoke:WikidataIB |getValue |rank=best |P159 |name=hq_location_city |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }} |<div class="ib-company-locality locality">{{#invoke:WikidataIB |getValue |rank=best |P159 |name=headquarters location |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }}</div>}} | 2 = {{#if:{{#invoke:WikidataIB |getValue |rank=best |P17 |name=countrry |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }} |<div class="ib-company-country country-name">{{#invoke:WikidataIB |getValue |rank=best |P17 |name=hq_location_country |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }}</div>}} }} }} }} | label19 = {{longitem|Number of locations}} | data19 = {{#if:{{{num_locations|{{{locations|}}}}}} | {{{num_locations|{{{locations|}}}}}}{{#if:{{{num_locations_year|}}} | &nbsp;({{{num_locations_year}}}) }} }} | label20 = {{longitem|Area{{#if:{{{areas_served|}}}|s}} served}} | data20 = {{{areas_served|{{{area_served|}}}}}} | label21 = {{longitem|Key people}} | class21 = agent | data21 = {{{key_people|}}} | label22 = Products | data22 = {{{products|}}} | label23 = {{longitem|Production output}} | data23 = {{#if:{{{production|}}} | {{{production|}}}{{#if:{{{production_year|}}} | &nbsp;({{{production_year}}}) }} }} | label24 = Brands | data24 = {{{brands|}}} | label25 = Services | class25 = category | data25 = {{{services|}}} | label26 = Revenue | data26 = {{#invoke:WikidataIB |getValue |rank=best |P2139 |name=revenue |qual=P585 |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 |{{{revenue|}}}{{#if:{{{revenue_year|}}} | &nbsp;({{{revenue_year}}}) }} }} | label27 = {{longitem|[[Earnings before interest and taxes|Operating income]]}} | data27 = {{#invoke:WikidataIB |getValue |rank=best |P3362 |name=operating_income |qual=P585 |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 |{{{operating_income|}}}{{#if:{{{income_year|}}} | &nbsp;({{{income_year}}}) }} }} | label28 = {{longitem|[[Net income]]}} | data28 = {{#invoke:WikidataIB |getValue |rank=best |P2295 |name=net_income |qual=P585 |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 |{{{net_income|{{{profit|}}}}}}{{#if:{{{net_income_year|{{{profit_year|}}}}}} | &nbsp;({{{net_income_year|{{{profit_year|}}}}}}) }} }} | label29 = [[Assets under management|AUM]] | data29 = {{{aum|}}} | label30 = {{nowrap|[[Asset|Total assets]]}} | data30 = {{#invoke:WikidataIB |getValue |rank=best |P2403 |name=assets |qual=P585 |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 |{{{assets|}}}{{#if:{{{assets_year|}}} | &nbsp;({{{assets_year}}}) }} }} | label31 = {{nowrap|[[Equity (finance)|Total equity]]}} | data31 = {{#if:{{{equity|}}} | {{{equity}}}{{#if:{{{equity_year|}}} | &nbsp;({{{equity_year}}})}} }} | label32 = Owner{{#if:{{{owners|}}}|s}} | data32 = {{{owners|{{{owner|}}}}}} | label33 = Members | data33 = {{#if:{{{members|}}} | {{{members}}}{{#if:{{{members_year|}}} | &nbsp;({{{members_year}}})}} }} | label34 = {{longitem|Number of employees}} | data34 = {{#invoke:WikidataIB |getValue |rank=best |P1128 |name=num_employees |qual=P585 |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 |{{{num_employees|}}}{{#if:{{{num_employees_year|}}} | &nbsp;({{{num_employees_year}}}) }} }} | label35 = [[Parent company|Parent]] | data35 = {{#ifeq:{{{owners|}}}{{{owner|}}}{{{parent|}}} || {{#invoke:WikidataIB |getValue |rank=best |P749 |name=parent |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes }} | {{{parent|}}} }} | label36 = [[Division (business)|Divisions]] | data36 = {{{divisions|}}} | label37 = [[Subsidiary|Subsidiaries]] | data37 = {{{subsid|{{{subsidiaries|}}}}}} | data39 = {{{module|}}} | label40 = [[Basel III|Capital ratio]] | data40 = {{{ratio|}}} | label41 = Rating | data41 = {{{rating|}}} | label42 = Website | data42 = {{#if:{{{website|{{{homepage|}}}}}} |{{{website|{{{homepage|}}}}}} |{{#invoke:WikidataIB |url2 |url={{#invoke:WikidataIB |getValue |rank=best |P856 |name=website |qid={{{qid|}}} |suppressfields={{{suppressfields|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |onlysourced=yes |maxvals=1}} }} }} | below = {{#if:{{{footnotes|}}} | '''Footnotes{{\}}references'''{{break}}{{{footnotes}}} }} }}<!-- Tracking categories: -->{{main other|{{#if:{{{trading_name|}}}|[[Category:Pages using infobox company using trading name]] }}{{#ifeq:{{{logo|{{{company_logo|{{wikidata|property|raw|P154}}}}}}}}|{{{logo|{{{company_logo|}}}}}}||[[Category:Pages using infobox company with a logo from wikidata]] }}{{#if:{{{image|}}}|{{#ifeq:{{#invoke:string|replace|{{{image|}}}| |_}}|{{#invoke:string|replace|{{{logo|{{{company_logo|{{wikidata|property|raw|P154}}}}}}}}| |_}}|[[Category:Pages using infobox company with a duplicate image]]|}}|}} }}<!-- -->{{#invoke:Check for clobbered parameters|check|nested=1|template=Infobox company|cat={{main other|Category:Pages using infobox company with conflicting parameters}}|name; company_name|logo; company_logo|logo_alt; alt|trade_name; trading_name|former_names; former_name|type; company_type|predecessors; predecessor|successors; successor|foundation; founded|founders; founder|defunct; dissolved|hq_location; location|hq_location_city; location_city|hq_location_country; location_country|num_locations; locations|areas_served; area_served|net_income; profit|net_income_year; profit_year|owners; owner |homepage; website }}{{#invoke:Check for unknown parameters|check|unknown={{main other|[[Category:Pages using infobox company with unknown parameters|_VALUE_{{PAGENAME}}]]}}|preview=Page using [[Template:Infobox company]] with unknown parameter "_VALUE_" | ignoreblank=y | alt | area_served | areas_served | assets | assets_year | aum | brands | company_logo | company_name | company_type | defunct | dissolved | divisions | embed | equity | equity_year | fate | footnotes | headquarters | former_name | former_names | foundation | founded | founder | founders | genre | homepage | hq_location | hq_location_city | hq_location_country | incorporated | image | image_alt | image_caption | image_size | image_upright | income_year | industry | ISIN | key_people | location | location_city | location_country | locations | logo | logo_alt | logo_caption | logo_class | logo_size | logo_upright | members | members_year | module | name | native_name | native_name_lang | net_income | net_income_year | num_employees | num_employees_year | num_locations | num_locations_year | operating_income | owner | owners | parent | predecessor | predecessors | production | production_year | products | profit | profit_year | rating | ratio | revenue | revenue_year | romanized_name | services | subsid | subsidiaries | successor | successors | traded_as | trade_name | trading_name | type | website| qid | fetchwikidata | suppressfields | noicon | nocat | demo | categories }}<noinclude> {{documentation}} </noinclude> ia9wgm4nycg114mnftmhq0lcwnnr1ev 803463 803462 2025-09-30T13:57:13Z en>Hike395 0 rm non-functional parameter 803463 wikitext text/x-wiki {{Infobox | bodyclass = ib-company vcard | templatestyles = Infobox company/styles.css | child = {{lc:{{{embed}}}}} | decat = yes | titleclass = fn org | titlestyle = font-size: 125%; | title = {{#ifeq:{{lc:{{{embed}}}}} | yes | '''Company''' | {{#if:{{{name|}}} | {{{name}}} | {{#if:{{{company_name|}}}|{{{company_name}}}|<includeonly>{{PAGENAMEBASE}}</includeonly>}} }} }} | imageclass = ib-company-logo logo | image = {{#invoke:InfoboxImage |InfoboxImage |image={{#ifeq:{{lc:{{{embed}}}}} | yes | {{{logo|{{{company_logo|}}}}}} |{{#invoke:WikidataIB |getValue |rank=best |P154 |name=logo |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=no |noicon=yes |maxvals=1 |{{{logo|{{{company_logo|}}}}}} }} }} |size={{{logo_size|}}} |sizedefault=frameless |upright={{{logo_upright|1}}} |alt={{{logo_alt|{{{alt|}}}}}} |class= {{{logo_class|}}} }} | caption = {{{logo_caption|}}} | image2 = {{#invoke:InfoboxImage |InfoboxImage |image={{{image|}}} |size={{{image_size|}}} |upright={{{image_upright|1}}} |alt={{{image_alt|}}} }} | caption2 = {{{image_caption|}}} | label1 = {{longitem|[[Trade name]]}} | data1 = {{{trade_name|{{{trading_name|}}}}}} | label2 = {{longitem|Native name}} | data2 = {{#if:{{{native_name|}}} | {{#if:{{{native_name_lang|}}} | {{lang|{{{native_name_lang}}}|{{{native_name}}} }} | {{{native_name}}} }} }} | label3 = {{longitem|[[Romanization|Romanized]] name}} | data3 = {{{romanized_name|}}} | label4 = Formerly | class4 = nickname | data4 = {{{former_names|{{{former_name|}}}}}} | label5 = Company type | class5 = category | data5 = {{{type|{{{company_type|}}}}}} | label6 = {{longitem|[[Ticker symbol|Traded as]]}} | data6 = {{{traded_as|}}} | label7 = [[International Securities Identification Number|ISIN]] | data7 = {{#invoke:WikidataIB |getValue |rank=best |P946 |name=ISIN |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{ISIN|}}} }} | label8 = Industry | class8 = category | data8 = {{#invoke:WikidataIB |getValue |rank=best |P452 |name=industry |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{industry|}}} }} | label9 = Genre | class9 = category | data9 = {{{genre|}}} | label10 = Predecessor{{#if:{{{predecessors|}}}|s}} | data10 = {{#invoke:WikidataIB |getValue |rank=best |P155 |name=predecessor |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{predecessors|{{{predecessor|}}}}}} }} | label11 = Incorporated | data11 = {{#invoke:WikidataIB |getValue |rank=best |P1001 |name=incorporated |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{incorporated|{{{incorporation|}}}}}} }} | label12 = Founded | data12 = {{#invoke:WikidataIB |getValue |rank=best |P571 |name=founded |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{founded|{{{foundation|}}}}}} }} | label13 = Founder{{#if:{{{founders|}}}|s}} | class13 = agent | data13 = {{#invoke:WikidataIB |getValue |rank=best |P112 |name=founder |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{founders|{{{founder|}}}}}} }} | label14 = Defunct | data14 = {{#invoke:WikidataIB |getValue |rank=best |P576 |name=defunct |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{defunct|{{{dissolved|}}}}}} }} | label15 = Fate | data15 = {{{fate|}}} | label16 = Successor{{#if:{{{successors|}}}|s}} | data16 = {{#invoke:WikidataIB |getValue |rank=best |P156 |name=successor |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{successors|{{{successor|}}}}}} }} | label17 = Headquarters | class17 = {{#if:{{{hq_location|{{{location|{{{headquarters|}}}}}}}}} | label | adr}} | data17 = {{#if:{{{hq_location|{{{location|{{{headquarters|}}}}}}}}}{{{hq_location_city|{{{location_city|}}}}}}{{{hq_location_country|{{{location_country|}}}}}} | {{Comma separated entries | 1 = {{{hq_location|{{{location|}}}}}} | 2 = {{#if:{{{hq_location_city|{{{headquarters|}}}}}}{{{location_city|}}} |<div class="ib-company-locality locality">{{#if:{{{hq_location_city|}}}{{{location_city|}}} | {{{hq_location_city|{{{location_city|}}}}}} | }}</div>}} | 3 = {{#if:{{{hq_location_country|}}}{{{location_country|}}} |<div class="ib-company-country country-name">{{#if:{{{hq_location_country|}}}{{{location_country|}}} | {{{hq_location_country|{{{location_country|}}}}}} | }}</div>}} }} | {{#if:{{#invoke:WikidataIB |getValue |rank=best |P159 |name=hq_location_city |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }}{{#invoke:WikidataIB |getValue |rank=best |P17 |name=hq_location_country |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }} | {{Comma separated entries | 1 = {{#if:{{#invoke:WikidataIB |getValue |rank=best |P159 |name=hq_location_city |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }} |<div class="ib-company-locality locality">{{#invoke:WikidataIB |getValue |rank=best |P159 |name=headquarters location |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }}</div>}} | 2 = {{#if:{{#invoke:WikidataIB |getValue |rank=best |P17 |name=countrry |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }} |<div class="ib-company-country country-name">{{#invoke:WikidataIB |getValue |rank=best |P17 |name=hq_location_country |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }}</div>}} }} }} }} | label19 = {{longitem|Number of locations}} | data19 = {{#if:{{{num_locations|{{{locations|}}}}}} | {{{num_locations|{{{locations|}}}}}}{{#if:{{{num_locations_year|}}} | &nbsp;({{{num_locations_year}}}) }} }} | label20 = {{longitem|Area{{#if:{{{areas_served|}}}|s}} served}} | data20 = {{{areas_served|{{{area_served|}}}}}} | label21 = {{longitem|Key people}} | class21 = agent | data21 = {{{key_people|}}} | label22 = Products | data22 = {{{products|}}} | label23 = {{longitem|Production output}} | data23 = {{#if:{{{production|}}} | {{{production|}}}{{#if:{{{production_year|}}} | &nbsp;({{{production_year}}}) }} }} | label24 = Brands | data24 = {{{brands|}}} | label25 = Services | class25 = category | data25 = {{{services|}}} | label26 = Revenue | data26 = {{#invoke:WikidataIB |getValue |rank=best |P2139 |name=revenue |qual=P585 |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 |{{{revenue|}}}{{#if:{{{revenue_year|}}} | &nbsp;({{{revenue_year}}}) }} }} | label27 = {{longitem|[[Earnings before interest and taxes|Operating income]]}} | data27 = {{#invoke:WikidataIB |getValue |rank=best |P3362 |name=operating_income |qual=P585 |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 |{{{operating_income|}}}{{#if:{{{income_year|}}} | &nbsp;({{{income_year}}}) }} }} | label28 = {{longitem|[[Net income]]}} | data28 = {{#invoke:WikidataIB |getValue |rank=best |P2295 |name=net_income |qual=P585 |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 |{{{net_income|{{{profit|}}}}}}{{#if:{{{net_income_year|{{{profit_year|}}}}}} | &nbsp;({{{net_income_year|{{{profit_year|}}}}}}) }} }} | label29 = [[Assets under management|AUM]] | data29 = {{{aum|}}} | label30 = {{nowrap|[[Asset|Total assets]]}} | data30 = {{#invoke:WikidataIB |getValue |rank=best |P2403 |name=assets |qual=P585 |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 |{{{assets|}}}{{#if:{{{assets_year|}}} | &nbsp;({{{assets_year}}}) }} }} | label31 = {{nowrap|[[Equity (finance)|Total equity]]}} | data31 = {{#if:{{{equity|}}} | {{{equity}}}{{#if:{{{equity_year|}}} | &nbsp;({{{equity_year}}})}} }} | label32 = Owner{{#if:{{{owners|}}}|s}} | data32 = {{{owners|{{{owner|}}}}}} | label33 = Members | data33 = {{#if:{{{members|}}} | {{{members}}}{{#if:{{{members_year|}}} | &nbsp;({{{members_year}}})}} }} | label34 = {{longitem|Number of employees}} | data34 = {{#invoke:WikidataIB |getValue |rank=best |P1128 |name=num_employees |qual=P585 |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 |{{{num_employees|}}}{{#if:{{{num_employees_year|}}} | &nbsp;({{{num_employees_year}}}) }} }} | label35 = [[Parent company|Parent]] | data35 = {{#ifeq:{{{owners|}}}{{{owner|}}}{{{parent|}}} || {{#invoke:WikidataIB |getValue |rank=best |P749 |name=parent |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes }} | {{{parent|}}} }} | label36 = [[Division (business)|Divisions]] | data36 = {{{divisions|}}} | label37 = [[Subsidiary|Subsidiaries]] | data37 = {{{subsid|{{{subsidiaries|}}}}}} | data39 = {{{module|}}} | label40 = [[Basel III|Capital ratio]] | data40 = {{{ratio|}}} | label41 = Rating | data41 = {{{rating|}}} | label42 = Website | data42 = {{#if:{{{website|{{{homepage|}}}}}} |{{{website|{{{homepage|}}}}}} |{{#invoke:WikidataIB |url2 |url={{#invoke:WikidataIB |getValue |rank=best |P856 |name=website |qid={{{qid|}}} |suppressfields={{{suppressfields|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |onlysourced=yes |maxvals=1}} }} }} | below = {{#if:{{{footnotes|}}} | '''Footnotes{{\}}references'''{{break}}{{{footnotes}}} }} }}<!-- Tracking categories: -->{{main other|{{#if:{{{trading_name|}}}|[[Category:Pages using infobox company using trading name]] }}{{#ifeq:{{{logo|{{{company_logo|{{wikidata|property|raw|P154}}}}}}}}|{{{logo|{{{company_logo|}}}}}}||[[Category:Pages using infobox company with a logo from wikidata]] }}{{#if:{{{image|}}}|{{#ifeq:{{#invoke:string|replace|{{{image|}}}| |_}}|{{#invoke:string|replace|{{{logo|{{{company_logo|{{wikidata|property|raw|P154}}}}}}}}| |_}}|[[Category:Pages using infobox company with a duplicate image]]|}}|}} }}<!-- -->{{#invoke:Check for clobbered parameters|check|nested=1|template=Infobox company|cat={{main other|Category:Pages using infobox company with conflicting parameters}}|name; company_name|logo; company_logo|logo_alt; alt|trade_name; trading_name|former_names; former_name|type; company_type|predecessors; predecessor|successors; successor|foundation; founded|founders; founder|defunct; dissolved|hq_location; location|hq_location_city; location_city|hq_location_country; location_country|num_locations; locations|areas_served; area_served|net_income; profit|net_income_year; profit_year|owners; owner |homepage; website }}{{#invoke:Check for unknown parameters|check|unknown={{main other|[[Category:Pages using infobox company with unknown parameters|_VALUE_{{PAGENAME}}]]}}|preview=Page using [[Template:Infobox company]] with unknown parameter "_VALUE_" | ignoreblank=y | alt | area_served | areas_served | assets | assets_year | aum | brands | company_logo | company_name | company_type | defunct | dissolved | divisions | embed | equity | equity_year | fate | footnotes | headquarters | former_name | former_names | foundation | founded | founder | founders | genre | homepage | hq_location | hq_location_city | hq_location_country | incorporated | image | image_alt | image_caption | image_size | image_upright | income_year | industry | ISIN | key_people | location | location_city | location_country | locations | logo | logo_alt | logo_caption | logo_class | logo_size | logo_upright | members | members_year | module | name | native_name | native_name_lang | net_income | net_income_year | num_employees | num_employees_year | num_locations | num_locations_year | operating_income | owner | owners | parent | predecessor | predecessors | production | production_year | products | profit | profit_year | rating | ratio | revenue | revenue_year | romanized_name | services | subsid | subsidiaries | successor | successors | traded_as | trade_name | trading_name | type | website| qid | fetchwikidata | suppressfields | noicon | nocat | demo | categories }}<noinclude> {{documentation}} </noinclude> po1rzgo25leduhy09jz6r9jnxk4wjdu 803464 803463 2025-10-01T06:37:57Z en>Zackmann08 0 fixing error where {{{headquarters|}}} Was not actually displaying 803464 wikitext text/x-wiki {{Infobox | bodyclass = ib-company vcard | templatestyles = Infobox company/styles.css | child = {{lc:{{{embed}}}}} | decat = yes | titleclass = fn org | titlestyle = font-size: 125%; | title = {{#ifeq:{{lc:{{{embed}}}}} | yes | '''Company''' | {{#if:{{{name|}}} | {{{name}}} | {{#if:{{{company_name|}}}|{{{company_name}}}|<includeonly>{{PAGENAMEBASE}}</includeonly>}} }} }} | imageclass = ib-company-logo logo | image = {{#invoke:InfoboxImage |InfoboxImage |image={{#ifeq:{{lc:{{{embed}}}}} | yes | {{{logo|{{{company_logo|}}}}}} |{{#invoke:WikidataIB |getValue |rank=best |P154 |name=logo |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=no |noicon=yes |maxvals=1 |{{{logo|{{{company_logo|}}}}}} }} }} |size={{{logo_size|}}} |sizedefault=frameless |upright={{{logo_upright|1}}} |alt={{{logo_alt|{{{alt|}}}}}} |class= {{{logo_class|}}} }} | caption = {{{logo_caption|}}} | image2 = {{#invoke:InfoboxImage |InfoboxImage |image={{{image|}}} |size={{{image_size|}}} |upright={{{image_upright|1}}} |alt={{{image_alt|}}} }} | caption2 = {{{image_caption|}}} | label1 = {{longitem|[[Trade name]]}} | data1 = {{{trade_name|{{{trading_name|}}}}}} | label2 = {{longitem|Native name}} | data2 = {{#if:{{{native_name|}}} | {{#if:{{{native_name_lang|}}} | {{lang|{{{native_name_lang}}}|{{{native_name}}} }} | {{{native_name}}} }} }} | label3 = {{longitem|[[Romanization|Romanized]] name}} | data3 = {{{romanized_name|}}} | label4 = Formerly | class4 = nickname | data4 = {{{former_names|{{{former_name|}}}}}} | label5 = Company type | class5 = category | data5 = {{{type|{{{company_type|}}}}}} | label6 = {{longitem|[[Ticker symbol|Traded as]]}} | data6 = {{{traded_as|}}} | label7 = [[International Securities Identification Number|ISIN]] | data7 = {{#invoke:WikidataIB |getValue |rank=best |P946 |name=ISIN |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{ISIN|}}} }} | label8 = Industry | class8 = category | data8 = {{#invoke:WikidataIB |getValue |rank=best |P452 |name=industry |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{industry|}}} }} | label9 = Genre | class9 = category | data9 = {{{genre|}}} | label10 = Predecessor{{#if:{{{predecessors|}}}|s}} | data10 = {{#invoke:WikidataIB |getValue |rank=best |P155 |name=predecessor |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{predecessors|{{{predecessor|}}}}}} }} | label11 = Incorporated | data11 = {{#invoke:WikidataIB |getValue |rank=best |P1001 |name=incorporated |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{incorporated|{{{incorporation|}}}}}} }} | label12 = Founded | data12 = {{#invoke:WikidataIB |getValue |rank=best |P571 |name=founded |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{founded|{{{foundation|}}}}}} }} | label13 = Founder{{#if:{{{founders|}}}|s}} | class13 = agent | data13 = {{#invoke:WikidataIB |getValue |rank=best |P112 |name=founder |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{founders|{{{founder|}}}}}} }} | label14 = Defunct | data14 = {{#invoke:WikidataIB |getValue |rank=best |P576 |name=defunct |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{defunct|{{{dissolved|}}}}}} }} | label15 = Fate | data15 = {{{fate|}}} | label16 = Successor{{#if:{{{successors|}}}|s}} | data16 = {{#invoke:WikidataIB |getValue |rank=best |P156 |name=successor |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{successors|{{{successor|}}}}}} }} | label17 = Headquarters | class17 = {{#if:{{{hq_location|{{{location|{{{headquarters|}}}}}}}}} | label | adr}} | data17 = {{#if:{{{hq_location|{{{location|{{{headquarters|}}}}}}}}}{{{hq_location_city|{{{location_city|}}}}}}{{{hq_location_country|{{{location_country|}}}}}} | {{Comma separated entries | 1 = {{{hq_location|{{{location|{{{headquarters|}}}}}}}}} | 2 = {{#if:{{{hq_location_city|}}}{{{location_city|}}} |<div class="ib-company-locality locality">{{#if:{{{hq_location_city|}}}{{{location_city|}}} | {{{hq_location_city|{{{location_city|}}}}}} | }}</div>}} | 3 = {{#if:{{{hq_location_country|}}}{{{location_country|}}} |<div class="ib-company-country country-name">{{#if:{{{hq_location_country|}}}{{{location_country|}}} | {{{hq_location_country|{{{location_country|}}}}}} | }}</div>}} }} | {{#if:{{#invoke:WikidataIB |getValue |rank=best |P159 |name=hq_location_city |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }}{{#invoke:WikidataIB |getValue |rank=best |P17 |name=hq_location_country |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }} | {{Comma separated entries | 1 = {{#if:{{#invoke:WikidataIB |getValue |rank=best |P159 |name=hq_location_city |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }} |<div class="ib-company-locality locality">{{#invoke:WikidataIB |getValue |rank=best |P159 |name=headquarters location |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }}</div>}} | 2 = {{#if:{{#invoke:WikidataIB |getValue |rank=best |P17 |name=countrry |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }} |<div class="ib-company-country country-name">{{#invoke:WikidataIB |getValue |rank=best |P17 |name=hq_location_country |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }}</div>}} }} }} }} | label19 = {{longitem|Number of locations}} | data19 = {{#if:{{{num_locations|{{{locations|}}}}}} | {{{num_locations|{{{locations|}}}}}}{{#if:{{{num_locations_year|}}} | &nbsp;({{{num_locations_year}}}) }} }} | label20 = {{longitem|Area{{#if:{{{areas_served|}}}|s}} served}} | data20 = {{{areas_served|{{{area_served|}}}}}} | label21 = {{longitem|Key people}} | class21 = agent | data21 = {{{key_people|}}} | label22 = Products | data22 = {{{products|}}} | label23 = {{longitem|Production output}} | data23 = {{#if:{{{production|}}} | {{{production|}}}{{#if:{{{production_year|}}} | &nbsp;({{{production_year}}}) }} }} | label24 = Brands | data24 = {{{brands|}}} | label25 = Services | class25 = category | data25 = {{{services|}}} | label26 = Revenue | data26 = {{#invoke:WikidataIB |getValue |rank=best |P2139 |name=revenue |qual=P585 |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 |{{{revenue|}}}{{#if:{{{revenue_year|}}} | &nbsp;({{{revenue_year}}}) }} }} | label27 = {{longitem|[[Earnings before interest and taxes|Operating income]]}} | data27 = {{#invoke:WikidataIB |getValue |rank=best |P3362 |name=operating_income |qual=P585 |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 |{{{operating_income|}}}{{#if:{{{income_year|}}} | &nbsp;({{{income_year}}}) }} }} | label28 = {{longitem|[[Net income]]}} | data28 = {{#invoke:WikidataIB |getValue |rank=best |P2295 |name=net_income |qual=P585 |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 |{{{net_income|{{{profit|}}}}}}{{#if:{{{net_income_year|{{{profit_year|}}}}}} | &nbsp;({{{net_income_year|{{{profit_year|}}}}}}) }} }} | label29 = [[Assets under management|AUM]] | data29 = {{{aum|}}} | label30 = {{nowrap|[[Asset|Total assets]]}} | data30 = {{#invoke:WikidataIB |getValue |rank=best |P2403 |name=assets |qual=P585 |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 |{{{assets|}}}{{#if:{{{assets_year|}}} | &nbsp;({{{assets_year}}}) }} }} | label31 = {{nowrap|[[Equity (finance)|Total equity]]}} | data31 = {{#if:{{{equity|}}} | {{{equity}}}{{#if:{{{equity_year|}}} | &nbsp;({{{equity_year}}})}} }} | label32 = Owner{{#if:{{{owners|}}}|s}} | data32 = {{{owners|{{{owner|}}}}}} | label33 = Members | data33 = {{#if:{{{members|}}} | {{{members}}}{{#if:{{{members_year|}}} | &nbsp;({{{members_year}}})}} }} | label34 = {{longitem|Number of employees}} | data34 = {{#invoke:WikidataIB |getValue |rank=best |P1128 |name=num_employees |qual=P585 |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 |{{{num_employees|}}}{{#if:{{{num_employees_year|}}} | &nbsp;({{{num_employees_year}}}) }} }} | label35 = [[Parent company|Parent]] | data35 = {{#ifeq:{{{owners|}}}{{{owner|}}}{{{parent|}}} || {{#invoke:WikidataIB |getValue |rank=best |P749 |name=parent |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes }} | {{{parent|}}} }} | label36 = [[Division (business)|Divisions]] | data36 = {{{divisions|}}} | label37 = [[Subsidiary|Subsidiaries]] | data37 = {{{subsid|{{{subsidiaries|}}}}}} | data39 = {{{module|}}} | label40 = [[Basel III|Capital ratio]] | data40 = {{{ratio|}}} | label41 = Rating | data41 = {{{rating|}}} | label42 = Website | data42 = {{#if:{{{website|{{{homepage|}}}}}} |{{{website|{{{homepage|}}}}}} |{{#invoke:WikidataIB |url2 |url={{#invoke:WikidataIB |getValue |rank=best |P856 |name=website |qid={{{qid|}}} |suppressfields={{{suppressfields|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |onlysourced=yes |maxvals=1}} }} }} | below = {{#if:{{{footnotes|}}} | '''Footnotes{{\}}references'''{{break}}{{{footnotes}}} }} }}<!-- Tracking categories: -->{{main other|{{#if:{{{trading_name|}}}|[[Category:Pages using infobox company using trading name]] }}{{#ifeq:{{{logo|{{{company_logo|{{wikidata|property|raw|P154}}}}}}}}|{{{logo|{{{company_logo|}}}}}}||[[Category:Pages using infobox company with a logo from wikidata]] }}{{#if:{{{image|}}}|{{#ifeq:{{#invoke:string|replace|{{{image|}}}| |_}}|{{#invoke:string|replace|{{{logo|{{{company_logo|{{wikidata|property|raw|P154}}}}}}}}| |_}}|[[Category:Pages using infobox company with a duplicate image]]|}}|}} }}<!-- -->{{#invoke:Check for clobbered parameters|check|nested=1|template=Infobox company|cat={{main other|Category:Pages using infobox company with conflicting parameters}}|name; company_name|logo; company_logo|logo_alt; alt|trade_name; trading_name|former_names; former_name|type; company_type|predecessors; predecessor|successors; successor|foundation; founded|founders; founder|defunct; dissolved|hq_location; location|hq_location_city; location_city|hq_location_country; location_country|num_locations; locations|areas_served; area_served|net_income; profit|net_income_year; profit_year|owners; owner |homepage; website }}{{#invoke:Check for unknown parameters|check|unknown={{main other|[[Category:Pages using infobox company with unknown parameters|_VALUE_{{PAGENAME}}]]}}|preview=Page using [[Template:Infobox company]] with unknown parameter "_VALUE_" | ignoreblank=y | alt | area_served | areas_served | assets | assets_year | aum | brands | company_logo | company_name | company_type | defunct | dissolved | divisions | embed | equity | equity_year | fate | footnotes | headquarters | former_name | former_names | foundation | founded | founder | founders | genre | homepage | hq_location | hq_location_city | hq_location_country | incorporated | image | image_alt | image_caption | image_size | image_upright | income_year | industry | ISIN | key_people | location | location_city | location_country | locations | logo | logo_alt | logo_caption | logo_class | logo_size | logo_upright | members | members_year | module | name | native_name | native_name_lang | net_income | net_income_year | num_employees | num_employees_year | num_locations | num_locations_year | operating_income | owner | owners | parent | predecessor | predecessors | production | production_year | products | profit | profit_year | rating | ratio | revenue | revenue_year | romanized_name | services | subsid | subsidiaries | successor | successors | traded_as | trade_name | trading_name | type | website| qid | fetchwikidata | suppressfields | noicon | nocat | demo | categories }}<noinclude> {{documentation}} </noinclude> 7bmzzcvusd78oa0srl11hnban69qp4e 803465 803464 2025-12-01T17:58:57Z en>Zackmann08 0 Max size for logo after [[Special:PermanentLink/1325190869|this edit]] 803465 wikitext text/x-wiki {{Infobox | bodyclass = ib-company vcard | templatestyles = Infobox company/styles.css | child = {{lc:{{{embed}}}}} | decat = yes | titleclass = fn org | titlestyle = font-size: 125%; | title = {{#ifeq:{{lc:{{{embed}}}}} | yes | '''Company''' | {{#if:{{{name|}}} | {{{name}}} | {{#if:{{{company_name|}}}|{{{company_name}}}|<includeonly>{{PAGENAMEBASE}}</includeonly>}} }} }} | imageclass = ib-company-logo logo | image = {{#invoke:InfoboxImage |InfoboxImage |image={{#ifeq:{{lc:{{{embed}}}}} | yes | {{{logo|{{{company_logo|}}}}}} |{{#invoke:WikidataIB |getValue |rank=best |P154 |name=logo |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=no |noicon=yes |maxvals=1 |{{{logo|{{{company_logo|}}}}}} }} }} |size={{{logo_size|}}} |sizedefault=frameless |upright={{{logo_upright|1}}} |alt={{{logo_alt|{{{alt|}}}}}} |class= {{{logo_class|}}} |maxsize=250px}} | caption = {{{logo_caption|}}} | image2 = {{#invoke:InfoboxImage |InfoboxImage |image={{{image|}}} |size={{{image_size|}}} |upright={{{image_upright|1}}} |alt={{{image_alt|}}} }} | caption2 = {{{image_caption|}}} | label1 = {{longitem|[[Trade name]]}} | data1 = {{{trade_name|{{{trading_name|}}}}}} | label2 = {{longitem|Native name}} | data2 = {{#if:{{{native_name|}}} | {{#if:{{{native_name_lang|}}} | {{lang|{{{native_name_lang}}}|{{{native_name}}} }} | {{{native_name}}} }} }} | label3 = {{longitem|[[Romanization|Romanized]] name}} | data3 = {{{romanized_name|}}} | label4 = Formerly | class4 = nickname | data4 = {{{former_names|{{{former_name|}}}}}} | label5 = Company type | class5 = category | data5 = {{{type|{{{company_type|}}}}}} | label6 = {{longitem|[[Ticker symbol|Traded as]]}} | data6 = {{{traded_as|}}} | label7 = [[International Securities Identification Number|ISIN]] | data7 = {{#invoke:WikidataIB |getValue |rank=best |P946 |name=ISIN |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{ISIN|}}} }} | label8 = Industry | class8 = category | data8 = {{#invoke:WikidataIB |getValue |rank=best |P452 |name=industry |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{industry|}}} }} | label9 = Genre | class9 = category | data9 = {{{genre|}}} | label10 = Predecessor{{#if:{{{predecessors|}}}|s}} | data10 = {{#invoke:WikidataIB |getValue |rank=best |P155 |name=predecessor |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{predecessors|{{{predecessor|}}}}}} }} | label11 = Incorporated | data11 = {{#invoke:WikidataIB |getValue |rank=best |P1001 |name=incorporated |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{incorporated|{{{incorporation|}}}}}} }} | label12 = Founded | data12 = {{#invoke:WikidataIB |getValue |rank=best |P571 |name=founded |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{founded|{{{foundation|}}}}}} }} | label13 = Founder{{#if:{{{founders|}}}|s}} | class13 = agent | data13 = {{#invoke:WikidataIB |getValue |rank=best |P112 |name=founder |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{founders|{{{founder|}}}}}} }} | label14 = Defunct | data14 = {{#invoke:WikidataIB |getValue |rank=best |P576 |name=defunct |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{defunct|{{{dissolved|}}}}}} }} | label15 = Fate | data15 = {{{fate|}}} | label16 = Successor{{#if:{{{successors|}}}|s}} | data16 = {{#invoke:WikidataIB |getValue |rank=best |P156 |name=successor |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes |{{{successors|{{{successor|}}}}}} }} | label17 = Headquarters | class17 = {{#if:{{{hq_location|{{{location|{{{headquarters|}}}}}}}}} | label | adr}} | data17 = {{#if:{{{hq_location|{{{location|{{{headquarters|}}}}}}}}}{{{hq_location_city|{{{location_city|}}}}}}{{{hq_location_country|{{{location_country|}}}}}} | {{Comma separated entries | 1 = {{{hq_location|{{{location|{{{headquarters|}}}}}}}}} | 2 = {{#if:{{{hq_location_city|}}}{{{location_city|}}} |<div class="ib-company-locality locality">{{#if:{{{hq_location_city|}}}{{{location_city|}}} | {{{hq_location_city|{{{location_city|}}}}}} | }}</div>}} | 3 = {{#if:{{{hq_location_country|}}}{{{location_country|}}} |<div class="ib-company-country country-name">{{#if:{{{hq_location_country|}}}{{{location_country|}}} | {{{hq_location_country|{{{location_country|}}}}}} | }}</div>}} }} | {{#if:{{#invoke:WikidataIB |getValue |rank=best |P159 |name=hq_location_city |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }}{{#invoke:WikidataIB |getValue |rank=best |P17 |name=hq_location_country |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }} | {{Comma separated entries | 1 = {{#if:{{#invoke:WikidataIB |getValue |rank=best |P159 |name=hq_location_city |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }} |<div class="ib-company-locality locality">{{#invoke:WikidataIB |getValue |rank=best |P159 |name=headquarters location |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }}</div>}} | 2 = {{#if:{{#invoke:WikidataIB |getValue |rank=best |P17 |name=countrry |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }} |<div class="ib-company-country country-name">{{#invoke:WikidataIB |getValue |rank=best |P17 |name=hq_location_country |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 }}</div>}} }} }} }} | label19 = {{longitem|Number of locations}} | data19 = {{#if:{{{num_locations|{{{locations|}}}}}} | {{{num_locations|{{{locations|}}}}}}{{#if:{{{num_locations_year|}}} | &nbsp;({{{num_locations_year}}}) }} }} | label20 = {{longitem|Area{{#if:{{{areas_served|}}}|s}} served}} | data20 = {{{areas_served|{{{area_served|}}}}}} | label21 = {{longitem|Key people}} | class21 = agent | data21 = {{{key_people|}}} | label22 = Products | data22 = {{{products|}}} | label23 = {{longitem|Production output}} | data23 = {{#if:{{{production|}}} | {{{production|}}}{{#if:{{{production_year|}}} | &nbsp;({{{production_year}}}) }} }} | label24 = Brands | data24 = {{{brands|}}} | label25 = Services | class25 = category | data25 = {{{services|}}} | label26 = Revenue | data26 = {{#invoke:WikidataIB |getValue |rank=best |P2139 |name=revenue |qual=P585 |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 |{{{revenue|}}}{{#if:{{{revenue_year|}}} | &nbsp;({{{revenue_year}}}) }} }} | label27 = {{longitem|[[Earnings before interest and taxes|Operating income]]}} | data27 = {{#invoke:WikidataIB |getValue |rank=best |P3362 |name=operating_income |qual=P585 |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 |{{{operating_income|}}}{{#if:{{{income_year|}}} | &nbsp;({{{income_year}}}) }} }} | label28 = {{longitem|[[Net income]]}} | data28 = {{#invoke:WikidataIB |getValue |rank=best |P2295 |name=net_income |qual=P585 |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 |{{{net_income|{{{profit|}}}}}}{{#if:{{{net_income_year|{{{profit_year|}}}}}} | &nbsp;({{{net_income_year|{{{profit_year|}}}}}}) }} }} | label29 = [[Assets under management|AUM]] | data29 = {{{aum|}}} | label30 = {{nowrap|[[Asset|Total assets]]}} | data30 = {{#invoke:WikidataIB |getValue |rank=best |P2403 |name=assets |qual=P585 |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 |{{{assets|}}}{{#if:{{{assets_year|}}} | &nbsp;({{{assets_year}}}) }} }} | label31 = {{nowrap|[[Equity (finance)|Total equity]]}} | data31 = {{#if:{{{equity|}}} | {{{equity}}}{{#if:{{{equity_year|}}} | &nbsp;({{{equity_year}}})}} }} | label32 = Owner{{#if:{{{owners|}}}|s}} | data32 = {{{owners|{{{owner|}}}}}} | label33 = Members | data33 = {{#if:{{{members|}}} | {{{members}}}{{#if:{{{members_year|}}} | &nbsp;({{{members_year}}})}} }} | label34 = {{longitem|Number of employees}} | data34 = {{#invoke:WikidataIB |getValue |rank=best |P1128 |name=num_employees |qual=P585 |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |maxvals=1 |{{{num_employees|}}}{{#if:{{{num_employees_year|}}} | &nbsp;({{{num_employees_year}}}) }} }} | label35 = [[Parent company|Parent]] | data35 = {{#ifeq:{{{owners|}}}{{{owner|}}}{{{parent|}}} || {{#invoke:WikidataIB |getValue |rank=best |P749 |name=parent |qid={{{qid|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{{noicon|no}}} |sep="<br />" |sorted=yes }} | {{{parent|}}} }} | label36 = [[Division (business)|Divisions]] | data36 = {{{divisions|}}} | label37 = [[Subsidiary|Subsidiaries]] | data37 = {{{subsid|{{{subsidiaries|}}}}}} | data39 = {{{module|}}} | label40 = [[Basel III|Capital ratio]] | data40 = {{{ratio|}}} | label41 = Rating | data41 = {{{rating|}}} | label42 = Website | data42 = {{#if:{{{website|{{{homepage|}}}}}} |{{{website|{{{homepage|}}}}}} |{{#invoke:WikidataIB |url2 |url={{#invoke:WikidataIB |getValue |rank=best |P856 |name=website |qid={{{qid|}}} |suppressfields={{{suppressfields|}}} |fetchwikidata={{{fetchwikidata|ALL}}} |onlysourced=yes |maxvals=1}} }} }} | below = {{#if:{{{footnotes|}}} | '''Footnotes{{\}}references'''{{break}}{{{footnotes}}} }} }}<!-- Tracking categories: -->{{main other|{{#if:{{{trading_name|}}}|[[Category:Pages using infobox company using trading name]] }}{{#ifeq:{{{logo|{{{company_logo|{{wikidata|property|raw|P154}}}}}}}}|{{{logo|{{{company_logo|}}}}}}||[[Category:Pages using infobox company with a logo from wikidata]] }}{{#if:{{{image|}}}|{{#ifeq:{{#invoke:string|replace|{{{image|}}}| |_}}|{{#invoke:string|replace|{{{logo|{{{company_logo|{{wikidata|property|raw|P154}}}}}}}}| |_}}|[[Category:Pages using infobox company with a duplicate image]]|}}|}} }}<!-- -->{{#invoke:Check for clobbered parameters|check|nested=1|template=Infobox company|cat={{main other|Category:Pages using infobox company with conflicting parameters}}|name; company_name|logo; company_logo|logo_alt; alt|trade_name; trading_name|former_names; former_name|type; company_type|predecessors; predecessor|successors; successor|foundation; founded|founders; founder|defunct; dissolved|hq_location; location|hq_location_city; location_city|hq_location_country; location_country|num_locations; locations|areas_served; area_served|net_income; profit|net_income_year; profit_year|owners; owner |homepage; website }}{{#invoke:Check for unknown parameters|check|unknown={{main other|[[Category:Pages using infobox company with unknown parameters|_VALUE_{{PAGENAME}}]]}}|preview=Page using [[Template:Infobox company]] with unknown parameter "_VALUE_" | ignoreblank=y | alt | area_served | areas_served | assets | assets_year | aum | brands | company_logo | company_name | company_type | defunct | dissolved | divisions | embed | equity | equity_year | fate | footnotes | headquarters | former_name | former_names | foundation | founded | founder | founders | genre | homepage | hq_location | hq_location_city | hq_location_country | incorporated | image | image_alt | image_caption | image_size | image_upright | income_year | industry | ISIN | key_people | location | location_city | location_country | locations | logo | logo_alt | logo_caption | logo_class | logo_size | logo_upright | members | members_year | module | name | native_name | native_name_lang | net_income | net_income_year | num_employees | num_employees_year | num_locations | num_locations_year | operating_income | owner | owners | parent | predecessor | predecessors | production | production_year | products | profit | profit_year | rating | ratio | revenue | revenue_year | romanized_name | services | subsid | subsidiaries | successor | successors | traded_as | trade_name | trading_name | type | website| qid | fetchwikidata | suppressfields | noicon | nocat | demo | categories }}<noinclude> {{documentation}} </noinclude> th0t20p5mznpa9tg8mrzdfo3r5q6hba 803466 803465 2025-12-09T07:17:16Z en>Zackmann08 0 Improving nested param logic using [[User:Zackmann08/unnest.js|unnest]] 803466 wikitext text/x-wiki {{Infobox | bodyclass = ib-company vcard | templatestyles = Infobox company/styles.css | child = {{lc:{{{embed}}}}} | decat = yes | titleclass = fn org | titlestyle = font-size: 125%; | title = {{#ifeq:{{lc:{{{embed}}}}} | yes | '''Company''' | {{#if:{{{name|}}} | {{{name}}} | {{#if:{{{company_name|}}}|{{{company_name}}}|<includeonly>{{PAGENAMEBASE}}</includeonly>}} }} }} | imageclass = ib-company-logo logo | image = {{#invoke:InfoboxImage |InfoboxImage |image={{#ifeq:{{lc:{{{embed}}}}} | yes | {{if empty|{{{logo|}}}|{{{company_logo|}}}}} |{{#invoke:WikidataIB |getValue |rank=best |P154 |name=logo |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=no |noicon=yes |maxvals=1 |{{if empty|{{{logo|}}}|{{{company_logo|}}}}} }} }} |size={{{logo_size|}}} |sizedefault=frameless |upright={{if empty|{{{logo_upright|}}}|1}} |alt={{if empty|{{{logo_alt|}}}|{{{alt|}}}}} |class= {{{logo_class|}}} |maxsize=250px}} | caption = {{{logo_caption|}}} | image2 = {{#invoke:InfoboxImage |InfoboxImage |image={{{image|}}} |size={{{image_size|}}} |upright={{if empty|{{{image_upright|}}}|1}} |alt={{{image_alt|}}} }} | caption2 = {{{image_caption|}}} | label1 = {{longitem|[[Trade name]]}} | data1 = {{if empty|{{{trade_name|}}}|{{{trading_name|}}}}} | label2 = {{longitem|Native name}} | data2 = {{#if:{{{native_name|}}} | {{#if:{{{native_name_lang|}}} | {{lang|{{{native_name_lang}}}|{{{native_name}}} }} | {{{native_name}}} }} }} | label3 = {{longitem|[[Romanization|Romanized]] name}} | data3 = {{{romanized_name|}}} | label4 = Formerly | class4 = nickname | data4 = {{if empty|{{{former_names|}}}|{{{former_name|}}}}} | label5 = Company type | class5 = category | data5 = {{if empty|{{{type|}}}|{{{company_type|}}}}} | label6 = {{longitem|[[Ticker symbol|Traded as]]}} | data6 = {{{traded_as|}}} | label7 = [[International Securities Identification Number|ISIN]] | data7 = {{#invoke:WikidataIB |getValue |rank=best |P946 |name=ISIN |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{{ISIN|}}} }} | label8 = Industry | class8 = category | data8 = {{#invoke:WikidataIB |getValue |rank=best |P452 |name=industry |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{{industry|}}} }} | label9 = Genre | class9 = category | data9 = {{{genre|}}} | label10 = Predecessor{{#if:{{{predecessors|}}}|s}} | data10 = {{#invoke:WikidataIB |getValue |rank=best |P155 |name=predecessor |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{predecessors|}}}|{{{predecessor|}}}}} }} | label11 = Incorporated | data11 = {{#invoke:WikidataIB |getValue |rank=best |P1001 |name=incorporated |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{incorporated|}}}|{{{incorporation|}}}}} }} | label12 = Founded | data12 = {{#invoke:WikidataIB |getValue |rank=best |P571 |name=founded |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{founded|}}}|{{{foundation|}}}}} }} | label13 = Founder{{#if:{{{founders|}}}|s}} | class13 = agent | data13 = {{#invoke:WikidataIB |getValue |rank=best |P112 |name=founder |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{founders|}}}|{{{founder|}}}}} }} | label14 = Defunct | data14 = {{#invoke:WikidataIB |getValue |rank=best |P576 |name=defunct |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{defunct|}}}|{{{dissolved|}}}}} }} | label15 = Fate | data15 = {{{fate|}}} | label16 = Successor{{#if:{{{successors|}}}|s}} | data16 = {{#invoke:WikidataIB |getValue |rank=best |P156 |name=successor |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{successors|}}}|{{{successor|}}}}} }} | label17 = Headquarters | class17 = {{#if:{{{hq_location|}}}{{{location|}}}{{{headquarters|}}} | label | adr}} | data17 = {{#if:{{{hq_location|}}}{{{location|}}}{{{headquarters|}}}{{{hq_location_city|}}}{{{location_city|}}}{{{hq_location_country|}}}{{{location_country|}}} | {{Comma separated entries | 1 = {{if empty|{{{hq_location|}}}|{{{location|}}}|{{{headquarters|}}}}} | 2 = {{#if:{{{hq_location_city|}}}{{{location_city|}}} |<div class="ib-company-locality locality">{{#if:{{{hq_location_city|}}}{{{location_city|}}} | {{if empty|{{{hq_location_city|}}}|{{{location_city|}}}}} | }}</div>}} | 3 = {{#if:{{{hq_location_country|}}}{{{location_country|}}} |<div class="ib-company-country country-name">{{#if:{{{hq_location_country|}}}{{{location_country|}}} | {{if empty|{{{hq_location_country|}}}|{{{location_country|}}}}} | }}</div>}} }} | {{#if:{{#invoke:WikidataIB |getValue |rank=best |P159 |name=hq_location_city |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }}{{#invoke:WikidataIB |getValue |rank=best |P17 |name=hq_location_country |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }} | {{Comma separated entries | 1 = {{#if:{{#invoke:WikidataIB |getValue |rank=best |P159 |name=hq_location_city |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }} |<div class="ib-company-locality locality">{{#invoke:WikidataIB |getValue |rank=best |P159 |name=headquarters location |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }}</div>}} | 2 = {{#if:{{#invoke:WikidataIB |getValue |rank=best |P17 |name=countrry |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }} |<div class="ib-company-country country-name">{{#invoke:WikidataIB |getValue |rank=best |P17 |name=hq_location_country |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }}</div>}} }} }} }} | label19 = {{longitem|Number of locations}} | data19 = {{#if:{{{num_locations|}}}{{{locations|}}} | {{if empty|{{{num_locations|}}}|{{{locations|}}}}}{{#if:{{{num_locations_year|}}} | &nbsp;({{{num_locations_year}}}) }} }} | label20 = {{longitem|Area{{#if:{{{areas_served|}}}|s}} served}} | data20 = {{if empty|{{{areas_served|}}}|{{{area_served|}}}}} | label21 = {{longitem|Key people}} | class21 = agent | data21 = {{{key_people|}}} | label22 = Products | data22 = {{{products|}}} | label23 = {{longitem|Production output}} | data23 = {{#if:{{{production|}}} | {{{production|}}}{{#if:{{{production_year|}}} | &nbsp;({{{production_year}}}) }} }} | label24 = Brands | data24 = {{{brands|}}} | label25 = Services | class25 = category | data25 = {{{services|}}} | label26 = Revenue | data26 = {{#invoke:WikidataIB |getValue |rank=best |P2139 |name=revenue |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{revenue|}}}{{#if:{{{revenue_year|}}} | &nbsp;({{{revenue_year}}}) }} }} | label27 = {{longitem|[[Earnings before interest and taxes|Operating income]]}} | data27 = {{#invoke:WikidataIB |getValue |rank=best |P3362 |name=operating_income |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{operating_income|}}}{{#if:{{{income_year|}}} | &nbsp;({{{income_year}}}) }} }} | label28 = {{longitem|[[Net income]]}} | data28 = {{#invoke:WikidataIB |getValue |rank=best |P2295 |name=net_income |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{if empty|{{{net_income|}}}|{{{profit|}}}}}{{#if:{{{net_income_year|}}}{{{profit_year|}}} | &nbsp;({{if empty|{{{net_income_year|}}}|{{{profit_year|}}}}}) }} }} | label29 = [[Assets under management|AUM]] | data29 = {{{aum|}}} | label30 = {{nowrap|[[Asset|Total assets]]}} | data30 = {{#invoke:WikidataIB |getValue |rank=best |P2403 |name=assets |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{assets|}}}{{#if:{{{assets_year|}}} | &nbsp;({{{assets_year}}}) }} }} | label31 = {{nowrap|[[Equity (finance)|Total equity]]}} | data31 = {{#if:{{{equity|}}} | {{{equity}}}{{#if:{{{equity_year|}}} | &nbsp;({{{equity_year}}})}} }} | label32 = Owner{{#if:{{{owners|}}}|s}} | data32 = {{if empty|{{{owners|}}}|{{{owner|}}}}} | label33 = Members | data33 = {{#if:{{{members|}}} | {{{members}}}{{#if:{{{members_year|}}} | &nbsp;({{{members_year}}})}} }} | label34 = {{longitem|Number of employees}} | data34 = {{#invoke:WikidataIB |getValue |rank=best |P1128 |name=num_employees |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{num_employees|}}}{{#if:{{{num_employees_year|}}} | &nbsp;({{{num_employees_year}}}) }} }} | label35 = [[Parent company|Parent]] | data35 = {{#ifeq:{{{owners|}}}{{{owner|}}}{{{parent|}}} || {{#invoke:WikidataIB |getValue |rank=best |P749 |name=parent |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes }} | {{{parent|}}} }} | label36 = [[Division (business)|Divisions]] | data36 = {{{divisions|}}} | label37 = [[Subsidiary|Subsidiaries]] | data37 = {{if empty|{{{subsid|}}}|{{{subsidiaries|}}}}} | data39 = {{{module|}}} | label40 = [[Basel III|Capital ratio]] | data40 = {{{ratio|}}} | label41 = Rating | data41 = {{{rating|}}} | label42 = Website | data42 = {{#if:{{{website|}}}{{{homepage|}}} |{{if empty|{{{website|}}}|{{{homepage|}}}}} |{{#invoke:WikidataIB |url2 |url={{#invoke:WikidataIB |getValue |rank=best |P856 |name=website |qid={{{qid|}}} |suppressfields={{{suppressfields|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |onlysourced=yes |maxvals=1}} }} }} | below = {{#if:{{{footnotes|}}} | '''Footnotes{{\}}references'''{{break}}{{{footnotes}}} }} }}<!-- Tracking categories: -->{{main other|{{#if:{{{trading_name|}}}|[[Category:Pages using infobox company using trading name]] }}{{#ifeq:{{if empty|{{{logo|}}}|{{{company_logo|}}}|{{wikidata|property|raw|P154}}}}|{{if empty|{{{logo|}}}|{{{company_logo|}}}}}||[[Category:Pages using infobox company with a logo from wikidata]] }}{{#if:{{{image|}}}|{{#ifeq:{{#invoke:string|replace|{{{image|}}}| |_}}|{{#invoke:string|replace|{{if empty|{{{logo|}}}|{{{company_logo|}}}|{{wikidata|property|raw|P154}}}}| |_}}|[[Category:Pages using infobox company with a duplicate image]]|}}|}} }}<!-- -->{{#invoke:Check for clobbered parameters|check|nested=1|template=Infobox company|cat={{main other|Category:Pages using infobox company with conflicting parameters}}|name; company_name|logo; company_logo|logo_alt; alt|trade_name; trading_name|former_names; former_name|type; company_type|predecessors; predecessor|successors; successor|foundation; founded|founders; founder|defunct; dissolved|hq_location; location|hq_location_city; location_city|hq_location_country; location_country|num_locations; locations|areas_served; area_served|net_income; profit|net_income_year; profit_year|owners; owner |homepage; website }}{{#invoke:Check for unknown parameters|check|unknown={{main other|[[Category:Pages using infobox company with unknown parameters|_VALUE_{{PAGENAME}}]]}}|preview=Page using [[Template:Infobox company]] with unknown parameter "_VALUE_" | ignoreblank=y | alt | area_served | areas_served | assets | assets_year | aum | brands | company_logo | company_name | company_type | defunct | dissolved | divisions | embed | equity | equity_year | fate | footnotes | headquarters | former_name | former_names | foundation | founded | founder | founders | genre | homepage | hq_location | hq_location_city | hq_location_country | incorporated | image | image_alt | image_caption | image_size | image_upright | income_year | industry | ISIN | key_people | location | location_city | location_country | locations | logo | logo_alt | logo_caption | logo_class | logo_size | logo_upright | members | members_year | module | name | native_name | native_name_lang | net_income | net_income_year | num_employees | num_employees_year | num_locations | num_locations_year | operating_income | owner | owners | parent | predecessor | predecessors | production | production_year | products | profit | profit_year | rating | ratio | revenue | revenue_year | romanized_name | services | subsid | subsidiaries | successor | successors | traded_as | trade_name | trading_name | type | website| qid | fetchwikidata | suppressfields | noicon | nocat | demo | categories }}<noinclude> {{documentation}} </noinclude> gc9jgpi2g0wi4o68fw4obx8pl1ojmex 803467 803466 2025-12-28T03:43:16Z en>Zackmann08 0 Module has been renamed 803467 wikitext text/x-wiki {{Infobox | bodyclass = ib-company vcard | templatestyles = Infobox company/styles.css | child = {{lc:{{{embed}}}}} | decat = yes | titleclass = fn org | titlestyle = font-size: 125%; | title = {{#ifeq:{{lc:{{{embed}}}}} | yes | '''Company''' | {{#if:{{{name|}}} | {{{name}}} | {{#if:{{{company_name|}}}|{{{company_name}}}|<includeonly>{{PAGENAMEBASE}}</includeonly>}} }} }} | imageclass = ib-company-logo logo | image = {{#invoke:InfoboxImage |InfoboxImage |image={{#ifeq:{{lc:{{{embed}}}}} | yes | {{if empty|{{{logo|}}}|{{{company_logo|}}}}} |{{#invoke:WikidataIB |getValue |rank=best |P154 |name=logo |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=no |noicon=yes |maxvals=1 |{{if empty|{{{logo|}}}|{{{company_logo|}}}}} }} }} |size={{{logo_size|}}} |sizedefault=frameless |upright={{if empty|{{{logo_upright|}}}|1}} |alt={{if empty|{{{logo_alt|}}}|{{{alt|}}}}} |class= {{{logo_class|}}} |maxsize=250px}} | caption = {{{logo_caption|}}} | image2 = {{#invoke:InfoboxImage |InfoboxImage |image={{{image|}}} |size={{{image_size|}}} |upright={{if empty|{{{image_upright|}}}|1}} |alt={{{image_alt|}}} }} | caption2 = {{{image_caption|}}} | label1 = {{longitem|[[Trade name]]}} | data1 = {{if empty|{{{trade_name|}}}|{{{trading_name|}}}}} | label2 = {{longitem|Native name}} | data2 = {{#if:{{{native_name|}}} | {{#if:{{{native_name_lang|}}} | {{lang|{{{native_name_lang}}}|{{{native_name}}} }} | {{{native_name}}} }} }} | label3 = {{longitem|[[Romanization|Romanized]] name}} | data3 = {{{romanized_name|}}} | label4 = Formerly | class4 = nickname | data4 = {{if empty|{{{former_names|}}}|{{{former_name|}}}}} | label5 = Company type | class5 = category | data5 = {{if empty|{{{type|}}}|{{{company_type|}}}}} | label6 = {{longitem|[[Ticker symbol|Traded as]]}} | data6 = {{{traded_as|}}} | label7 = [[International Securities Identification Number|ISIN]] | data7 = {{#invoke:WikidataIB |getValue |rank=best |P946 |name=ISIN |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{{ISIN|}}} }} | label8 = Industry | class8 = category | data8 = {{#invoke:WikidataIB |getValue |rank=best |P452 |name=industry |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{{industry|}}} }} | label9 = Genre | class9 = category | data9 = {{{genre|}}} | label10 = Predecessor{{#if:{{{predecessors|}}}|s}} | data10 = {{#invoke:WikidataIB |getValue |rank=best |P155 |name=predecessor |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{predecessors|}}}|{{{predecessor|}}}}} }} | label11 = Incorporated | data11 = {{#invoke:WikidataIB |getValue |rank=best |P1001 |name=incorporated |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{incorporated|}}}|{{{incorporation|}}}}} }} | label12 = Founded | data12 = {{#invoke:WikidataIB |getValue |rank=best |P571 |name=founded |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{founded|}}}|{{{foundation|}}}}} }} | label13 = Founder{{#if:{{{founders|}}}|s}} | class13 = agent | data13 = {{#invoke:WikidataIB |getValue |rank=best |P112 |name=founder |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{founders|}}}|{{{founder|}}}}} }} | label14 = Defunct | data14 = {{#invoke:WikidataIB |getValue |rank=best |P576 |name=defunct |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{defunct|}}}|{{{dissolved|}}}}} }} | label15 = Fate | data15 = {{{fate|}}} | label16 = Successor{{#if:{{{successors|}}}|s}} | data16 = {{#invoke:WikidataIB |getValue |rank=best |P156 |name=successor |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{successors|}}}|{{{successor|}}}}} }} | label17 = Headquarters | class17 = {{#if:{{{hq_location|}}}{{{location|}}}{{{headquarters|}}} | label | adr}} | data17 = {{#if:{{{hq_location|}}}{{{location|}}}{{{headquarters|}}}{{{hq_location_city|}}}{{{location_city|}}}{{{hq_location_country|}}}{{{location_country|}}} | {{Comma separated entries | 1 = {{if empty|{{{hq_location|}}}|{{{location|}}}|{{{headquarters|}}}}} | 2 = {{#if:{{{hq_location_city|}}}{{{location_city|}}} |<div class="ib-company-locality locality">{{#if:{{{hq_location_city|}}}{{{location_city|}}} | {{if empty|{{{hq_location_city|}}}|{{{location_city|}}}}} | }}</div>}} | 3 = {{#if:{{{hq_location_country|}}}{{{location_country|}}} |<div class="ib-company-country country-name">{{#if:{{{hq_location_country|}}}{{{location_country|}}} | {{if empty|{{{hq_location_country|}}}|{{{location_country|}}}}} | }}</div>}} }} | {{#if:{{#invoke:WikidataIB |getValue |rank=best |P159 |name=hq_location_city |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }}{{#invoke:WikidataIB |getValue |rank=best |P17 |name=hq_location_country |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }} | {{Comma separated entries | 1 = {{#if:{{#invoke:WikidataIB |getValue |rank=best |P159 |name=hq_location_city |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }} |<div class="ib-company-locality locality">{{#invoke:WikidataIB |getValue |rank=best |P159 |name=headquarters location |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }}</div>}} | 2 = {{#if:{{#invoke:WikidataIB |getValue |rank=best |P17 |name=countrry |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }} |<div class="ib-company-country country-name">{{#invoke:WikidataIB |getValue |rank=best |P17 |name=hq_location_country |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }}</div>}} }} }} }} | label19 = {{longitem|Number of locations}} | data19 = {{#if:{{{num_locations|}}}{{{locations|}}} | {{if empty|{{{num_locations|}}}|{{{locations|}}}}}{{#if:{{{num_locations_year|}}} | &nbsp;({{{num_locations_year}}}) }} }} | label20 = {{longitem|Area{{#if:{{{areas_served|}}}|s}} served}} | data20 = {{if empty|{{{areas_served|}}}|{{{area_served|}}}}} | label21 = {{longitem|Key people}} | class21 = agent | data21 = {{{key_people|}}} | label22 = Products | data22 = {{{products|}}} | label23 = {{longitem|Production output}} | data23 = {{#if:{{{production|}}} | {{{production|}}}{{#if:{{{production_year|}}} | &nbsp;({{{production_year}}}) }} }} | label24 = Brands | data24 = {{{brands|}}} | label25 = Services | class25 = category | data25 = {{{services|}}} | label26 = Revenue | data26 = {{#invoke:WikidataIB |getValue |rank=best |P2139 |name=revenue |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{revenue|}}}{{#if:{{{revenue_year|}}} | &nbsp;({{{revenue_year}}}) }} }} | label27 = {{longitem|[[Earnings before interest and taxes|Operating income]]}} | data27 = {{#invoke:WikidataIB |getValue |rank=best |P3362 |name=operating_income |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{operating_income|}}}{{#if:{{{income_year|}}} | &nbsp;({{{income_year}}}) }} }} | label28 = {{longitem|[[Net income]]}} | data28 = {{#invoke:WikidataIB |getValue |rank=best |P2295 |name=net_income |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{if empty|{{{net_income|}}}|{{{profit|}}}}}{{#if:{{{net_income_year|}}}{{{profit_year|}}} | &nbsp;({{if empty|{{{net_income_year|}}}|{{{profit_year|}}}}}) }} }} | label29 = [[Assets under management|AUM]] | data29 = {{{aum|}}} | label30 = {{nowrap|[[Asset|Total assets]]}} | data30 = {{#invoke:WikidataIB |getValue |rank=best |P2403 |name=assets |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{assets|}}}{{#if:{{{assets_year|}}} | &nbsp;({{{assets_year}}}) }} }} | label31 = {{nowrap|[[Equity (finance)|Total equity]]}} | data31 = {{#if:{{{equity|}}} | {{{equity}}}{{#if:{{{equity_year|}}} | &nbsp;({{{equity_year}}})}} }} | label32 = Owner{{#if:{{{owners|}}}|s}} | data32 = {{if empty|{{{owners|}}}|{{{owner|}}}}} | label33 = Members | data33 = {{#if:{{{members|}}} | {{{members}}}{{#if:{{{members_year|}}} | &nbsp;({{{members_year}}})}} }} | label34 = {{longitem|Number of employees}} | data34 = {{#invoke:WikidataIB |getValue |rank=best |P1128 |name=num_employees |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{num_employees|}}}{{#if:{{{num_employees_year|}}} | &nbsp;({{{num_employees_year}}}) }} }} | label35 = [[Parent company|Parent]] | data35 = {{#ifeq:{{{owners|}}}{{{owner|}}}{{{parent|}}} || {{#invoke:WikidataIB |getValue |rank=best |P749 |name=parent |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes }} | {{{parent|}}} }} | label36 = [[Division (business)|Divisions]] | data36 = {{{divisions|}}} | label37 = [[Subsidiary|Subsidiaries]] | data37 = {{if empty|{{{subsid|}}}|{{{subsidiaries|}}}}} | data39 = {{{module|}}} | label40 = [[Basel III|Capital ratio]] | data40 = {{{ratio|}}} | label41 = Rating | data41 = {{{rating|}}} | label42 = Website | data42 = {{#if:{{{website|}}}{{{homepage|}}} |{{if empty|{{{website|}}}|{{{homepage|}}}}} |{{#invoke:WikidataIB |url2 |url={{#invoke:WikidataIB |getValue |rank=best |P856 |name=website |qid={{{qid|}}} |suppressfields={{{suppressfields|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |onlysourced=yes |maxvals=1}} }} }} | below = {{#if:{{{footnotes|}}} | '''Footnotes{{\}}references'''{{break}}{{{footnotes}}} }} }}<!-- Tracking categories: -->{{main other|{{#if:{{{trading_name|}}}|[[Category:Pages using infobox company using trading name]] }}{{#ifeq:{{if empty|{{{logo|}}}|{{{company_logo|}}}|{{wikidata|property|raw|P154}}}}|{{if empty|{{{logo|}}}|{{{company_logo|}}}}}||[[Category:Pages using infobox company with a logo from wikidata]] }}{{#if:{{{image|}}}|{{#ifeq:{{#invoke:string|replace|{{{image|}}}| |_}}|{{#invoke:string|replace|{{if empty|{{{logo|}}}|{{{company_logo|}}}|{{wikidata|property|raw|P154}}}}| |_}}|[[Category:Pages using infobox company with a duplicate image]]|}}|}} }}<!-- -->{{#invoke:Check for conflicting parameters|check|nested=1|template=Infobox company|cat={{main other|Category:Pages using infobox company with conflicting parameters}}|name; company_name|logo; company_logo|logo_alt; alt|trade_name; trading_name|former_names; former_name|type; company_type|predecessors; predecessor|successors; successor|foundation; founded|founders; founder|defunct; dissolved|hq_location; location|hq_location_city; location_city|hq_location_country; location_country|num_locations; locations|areas_served; area_served|net_income; profit|net_income_year; profit_year|owners; owner |homepage; website }}{{#invoke:Check for unknown parameters|check|unknown={{main other|[[Category:Pages using infobox company with unknown parameters|_VALUE_{{PAGENAME}}]]}}|preview=Page using [[Template:Infobox company]] with unknown parameter "_VALUE_" | ignoreblank=y | alt | area_served | areas_served | assets | assets_year | aum | brands | company_logo | company_name | company_type | defunct | dissolved | divisions | embed | equity | equity_year | fate | footnotes | headquarters | former_name | former_names | foundation | founded | founder | founders | genre | homepage | hq_location | hq_location_city | hq_location_country | incorporated | image | image_alt | image_caption | image_size | image_upright | income_year | industry | ISIN | key_people | location | location_city | location_country | locations | logo | logo_alt | logo_caption | logo_class | logo_size | logo_upright | members | members_year | module | name | native_name | native_name_lang | net_income | net_income_year | num_employees | num_employees_year | num_locations | num_locations_year | operating_income | owner | owners | parent | predecessor | predecessors | production | production_year | products | profit | profit_year | rating | ratio | revenue | revenue_year | romanized_name | services | subsid | subsidiaries | successor | successors | traded_as | trade_name | trading_name | type | website| qid | fetchwikidata | suppressfields | noicon | nocat | demo | categories }}<noinclude> {{documentation}} </noinclude> iqhh23wtr9tb2bd72v7yd37ta5ct46y 803468 803467 2026-04-22T14:24:29Z en>Zackmann08 0 Adding [[Template:Longitem]] 803468 wikitext text/x-wiki {{Infobox | bodyclass = ib-company vcard | templatestyles = Infobox company/styles.css | child = {{lc:{{{embed}}}}} | decat = yes | titleclass = fn org | titlestyle = font-size: 125%; | title = {{#ifeq:{{lc:{{{embed}}}}} | yes | '''Company''' | {{#if:{{{name|}}} | {{{name}}} | {{#if:{{{company_name|}}}|{{{company_name}}}|<includeonly>{{PAGENAMEBASE}}</includeonly>}} }} }} | imageclass = ib-company-logo logo | image = {{#invoke:InfoboxImage |InfoboxImage |image={{#ifeq:{{lc:{{{embed}}}}} | yes | {{if empty|{{{logo|}}}|{{{company_logo|}}}}} |{{#invoke:WikidataIB |getValue |rank=best |P154 |name=logo |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=no |noicon=yes |maxvals=1 |{{if empty|{{{logo|}}}|{{{company_logo|}}}}} }} }} |size={{{logo_size|}}} |sizedefault=frameless |upright={{if empty|{{{logo_upright|}}}|1}} |alt={{if empty|{{{logo_alt|}}}|{{{alt|}}}}} |class= {{{logo_class|}}} |maxsize=250px}} | caption = {{{logo_caption|}}} | image2 = {{#invoke:InfoboxImage |InfoboxImage |image={{{image|}}} |size={{{image_size|}}} |upright={{if empty|{{{image_upright|}}}|1}} |alt={{{image_alt|}}} }} | caption2 = {{{image_caption|}}} | label1 = {{longitem|[[Trade name]]}} | data1 = {{if empty|{{{trade_name|}}}|{{{trading_name|}}}}} | label2 = {{longitem|Native name}} | data2 = {{#if:{{{native_name|}}} | {{#if:{{{native_name_lang|}}} | {{lang|{{{native_name_lang}}}|{{{native_name}}} }} | {{{native_name}}} }} }} | label3 = {{longitem|[[Romanization|Romanized]] name}} | data3 = {{{romanized_name|}}} | label4 = Formerly | class4 = nickname | data4 = {{if empty|{{{former_names|}}}|{{{former_name|}}}}} | label5 = {{longitem|Company type}} | class5 = category | data5 = {{if empty|{{{type|}}}|{{{company_type|}}}}} | label6 = {{longitem|[[Ticker symbol|Traded as]]}} | data6 = {{{traded_as|}}} | label7 = [[International Securities Identification Number|ISIN]] | data7 = {{#invoke:WikidataIB |getValue |rank=best |P946 |name=ISIN |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{{ISIN|}}} }} | label8 = Industry | class8 = category | data8 = {{#invoke:WikidataIB |getValue |rank=best |P452 |name=industry |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{{industry|}}} }} | label9 = Genre | class9 = category | data9 = {{{genre|}}} | label10 = Predecessor{{#if:{{{predecessors|}}}|s}} | data10 = {{#invoke:WikidataIB |getValue |rank=best |P155 |name=predecessor |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{predecessors|}}}|{{{predecessor|}}}}} }} | label11 = Incorporated | data11 = {{#invoke:WikidataIB |getValue |rank=best |P1001 |name=incorporated |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{incorporated|}}}|{{{incorporation|}}}}} }} | label12 = Founded | data12 = {{#invoke:WikidataIB |getValue |rank=best |P571 |name=founded |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{founded|}}}|{{{foundation|}}}}} }} | label13 = Founder{{#if:{{{founders|}}}|s}} | class13 = agent | data13 = {{#invoke:WikidataIB |getValue |rank=best |P112 |name=founder |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{founders|}}}|{{{founder|}}}}} }} | label14 = Defunct | data14 = {{#invoke:WikidataIB |getValue |rank=best |P576 |name=defunct |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{defunct|}}}|{{{dissolved|}}}}} }} | label15 = Fate | data15 = {{{fate|}}} | label16 = Successor{{#if:{{{successors|}}}|s}} | data16 = {{#invoke:WikidataIB |getValue |rank=best |P156 |name=successor |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{successors|}}}|{{{successor|}}}}} }} | label17 = Headquarters | class17 = {{#if:{{{hq_location|}}}{{{location|}}}{{{headquarters|}}} | label | adr}} | data17 = {{#if:{{{hq_location|}}}{{{location|}}}{{{headquarters|}}}{{{hq_location_city|}}}{{{location_city|}}}{{{hq_location_country|}}}{{{location_country|}}} | {{Comma separated entries | 1 = {{if empty|{{{hq_location|}}}|{{{location|}}}|{{{headquarters|}}}}} | 2 = {{#if:{{{hq_location_city|}}}{{{location_city|}}} |<div class="ib-company-locality locality">{{#if:{{{hq_location_city|}}}{{{location_city|}}} | {{if empty|{{{hq_location_city|}}}|{{{location_city|}}}}} | }}</div>}} | 3 = {{#if:{{{hq_location_country|}}}{{{location_country|}}} |<div class="ib-company-country country-name">{{#if:{{{hq_location_country|}}}{{{location_country|}}} | {{if empty|{{{hq_location_country|}}}|{{{location_country|}}}}} | }}</div>}} }} | {{#if:{{#invoke:WikidataIB |getValue |rank=best |P159 |name=hq_location_city |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }}{{#invoke:WikidataIB |getValue |rank=best |P17 |name=hq_location_country |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }} | {{Comma separated entries | 1 = {{#if:{{#invoke:WikidataIB |getValue |rank=best |P159 |name=hq_location_city |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }} |<div class="ib-company-locality locality">{{#invoke:WikidataIB |getValue |rank=best |P159 |name=headquarters location |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }}</div>}} | 2 = {{#if:{{#invoke:WikidataIB |getValue |rank=best |P17 |name=countrry |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }} |<div class="ib-company-country country-name">{{#invoke:WikidataIB |getValue |rank=best |P17 |name=hq_location_country |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }}</div>}} }} }} }} | label19 = {{longitem|Number of locations}} | data19 = {{#if:{{{num_locations|}}}{{{locations|}}} | {{if empty|{{{num_locations|}}}|{{{locations|}}}}}{{#if:{{{num_locations_year|}}} | &nbsp;({{{num_locations_year}}}) }} }} | label20 = {{longitem|Area{{#if:{{{areas_served|}}}|s}} served}} | data20 = {{if empty|{{{areas_served|}}}|{{{area_served|}}}}} | label21 = {{longitem|Key people}} | class21 = agent | data21 = {{{key_people|}}} | label22 = Products | data22 = {{{products|}}} | label23 = {{longitem|Production output}} | data23 = {{#if:{{{production|}}} | {{{production|}}}{{#if:{{{production_year|}}} | &nbsp;({{{production_year}}}) }} }} | label24 = Brands | data24 = {{{brands|}}} | label25 = Services | class25 = category | data25 = {{{services|}}} | label26 = Revenue | data26 = {{#invoke:WikidataIB |getValue |rank=best |P2139 |name=revenue |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{revenue|}}}{{#if:{{{revenue_year|}}} | &nbsp;({{{revenue_year}}}) }} }} | label27 = {{longitem|[[Earnings before interest and taxes|Operating income]]}} | data27 = {{#invoke:WikidataIB |getValue |rank=best |P3362 |name=operating_income |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{operating_income|}}}{{#if:{{{income_year|}}} | &nbsp;({{{income_year}}}) }} }} | label28 = {{longitem|[[Net income]]}} | data28 = {{#invoke:WikidataIB |getValue |rank=best |P2295 |name=net_income |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{if empty|{{{net_income|}}}|{{{profit|}}}}}{{#if:{{{net_income_year|}}}{{{profit_year|}}} | &nbsp;({{if empty|{{{net_income_year|}}}|{{{profit_year|}}}}}) }} }} | label29 = [[Assets under management|AUM]] | data29 = {{{aum|}}} | label30 = {{nowrap|[[Asset|Total assets]]}} | data30 = {{#invoke:WikidataIB |getValue |rank=best |P2403 |name=assets |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{assets|}}}{{#if:{{{assets_year|}}} | &nbsp;({{{assets_year}}}) }} }} | label31 = {{nowrap|[[Equity (finance)|Total equity]]}} | data31 = {{#if:{{{equity|}}} | {{{equity}}}{{#if:{{{equity_year|}}} | &nbsp;({{{equity_year}}})}} }} | label32 = Owner{{#if:{{{owners|}}}|s}} | data32 = {{if empty|{{{owners|}}}|{{{owner|}}}}} | label33 = Members | data33 = {{#if:{{{members|}}} | {{{members}}}{{#if:{{{members_year|}}} | &nbsp;({{{members_year}}})}} }} | label34 = {{longitem|Number of employees}} | data34 = {{#invoke:WikidataIB |getValue |rank=best |P1128 |name=num_employees |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{num_employees|}}}{{#if:{{{num_employees_year|}}} | &nbsp;({{{num_employees_year}}}) }} }} | label35 = [[Parent company|Parent]] | data35 = {{#ifeq:{{{owners|}}}{{{owner|}}}{{{parent|}}} || {{#invoke:WikidataIB |getValue |rank=best |P749 |name=parent |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes }} | {{{parent|}}} }} | label36 = [[Division (business)|Divisions]] | data36 = {{{divisions|}}} | label37 = [[Subsidiary|Subsidiaries]] | data37 = {{if empty|{{{subsid|}}}|{{{subsidiaries|}}}}} | data39 = {{{module|}}} | label40 = [[Basel III|Capital ratio]] | data40 = {{{ratio|}}} | label41 = Rating | data41 = {{{rating|}}} | label42 = Website | data42 = {{#if:{{{website|}}}{{{homepage|}}} |{{if empty|{{{website|}}}|{{{homepage|}}}}} |{{#invoke:WikidataIB |url2 |url={{#invoke:WikidataIB |getValue |rank=best |P856 |name=website |qid={{{qid|}}} |suppressfields={{{suppressfields|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |onlysourced=yes |maxvals=1}} }} }} | below = {{#if:{{{footnotes|}}} | '''Footnotes{{\}}references'''{{break}}{{{footnotes}}} }} }}<!-- Tracking categories: -->{{main other|{{#if:{{{trading_name|}}}|[[Category:Pages using infobox company using trading name]] }}{{#ifeq:{{if empty|{{{logo|}}}|{{{company_logo|}}}|{{wikidata|property|raw|P154}}}}|{{if empty|{{{logo|}}}|{{{company_logo|}}}}}||[[Category:Pages using infobox company with a logo from wikidata]] }}{{#if:{{{image|}}}|{{#ifeq:{{#invoke:string|replace|{{{image|}}}| |_}}|{{#invoke:string|replace|{{if empty|{{{logo|}}}|{{{company_logo|}}}|{{wikidata|property|raw|P154}}}}| |_}}|[[Category:Pages using infobox company with a duplicate image]]|}}|}} }}<!-- -->{{#invoke:Check for conflicting parameters|check|nested=1|template=Infobox company|cat={{main other|Category:Pages using infobox company with conflicting parameters}}|name; company_name|logo; company_logo|logo_alt; alt|trade_name; trading_name|former_names; former_name|type; company_type|predecessors; predecessor|successors; successor|foundation; founded|founders; founder|defunct; dissolved|hq_location; location|hq_location_city; location_city|hq_location_country; location_country|num_locations; locations|areas_served; area_served|net_income; profit|net_income_year; profit_year|owners; owner |homepage; website }}{{#invoke:Check for unknown parameters|check|unknown={{main other|[[Category:Pages using infobox company with unknown parameters|_VALUE_{{PAGENAME}}]]}}|preview=Page using [[Template:Infobox company]] with unknown parameter "_VALUE_" | ignoreblank=y | alt | area_served | areas_served | assets | assets_year | aum | brands | company_logo | company_name | company_type | defunct | dissolved | divisions | embed | equity | equity_year | fate | footnotes | headquarters | former_name | former_names | foundation | founded | founder | founders | genre | homepage | hq_location | hq_location_city | hq_location_country | incorporated | image | image_alt | image_caption | image_size | image_upright | income_year | industry | ISIN | key_people | location | location_city | location_country | locations | logo | logo_alt | logo_caption | logo_class | logo_size | logo_upright | members | members_year | module | name | native_name | native_name_lang | net_income | net_income_year | num_employees | num_employees_year | num_locations | num_locations_year | operating_income | owner | owners | parent | predecessor | predecessors | production | production_year | products | profit | profit_year | rating | ratio | revenue | revenue_year | romanized_name | services | subsid | subsidiaries | successor | successors | traded_as | trade_name | trading_name | type | website| qid | fetchwikidata | suppressfields | noicon | nocat | demo | categories }}<noinclude> {{documentation}} </noinclude> hyra2sgzdnibcyylmqdufrx6kdo9052 803469 803468 2026-04-25T22:02:59Z en>Zackmann08 0 Updating conflicting parameter tracking through [[:Category:Pages using infobox company with conflicting parameters]] using [[User:Zackmann08/scripts/AddCheckForConflictingParameters.js|AddCheckForConflictingParameters]] using [[Module:check for conflicting parameters]] 803469 wikitext text/x-wiki {{Infobox | bodyclass = ib-company vcard | templatestyles = Infobox company/styles.css | child = {{lc:{{{embed}}}}} | decat = yes | titleclass = fn org | titlestyle = font-size: 125%; | title = {{#ifeq:{{lc:{{{embed}}}}} | yes | '''Company''' | {{if empty|{{{name|}}}|{{{company_name|}}}|<includeonly>{{PAGENAMEBASE}}</includeonly>}} }} | imageclass = ib-company-logo logo | image = {{#invoke:InfoboxImage |InfoboxImage |image={{#ifeq:{{lc:{{{embed}}}}} | yes | {{if empty|{{{logo|}}}|{{{company_logo|}}}}} |{{#invoke:WikidataIB |getValue |rank=best |P154 |name=logo |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=no |noicon=yes |maxvals=1 |{{if empty|{{{logo|}}}|{{{company_logo|}}}}} }} }} |size={{{logo_size|}}} |sizedefault=frameless |upright={{if empty|{{{logo_upright|}}}|1}} |alt={{if empty|{{{logo_alt|}}}|{{{alt|}}}}} |class= {{{logo_class|}}} |maxsize=250px}} | caption = {{{logo_caption|}}} | image2 = {{#invoke:InfoboxImage |InfoboxImage |image={{{image|}}} |size={{{image_size|}}} |upright={{if empty|{{{image_upright|}}}|1}} |alt={{{image_alt|}}} }} | caption2 = {{{image_caption|}}} | label1 = {{longitem|[[Trade name]]}} | data1 = {{if empty|{{{trade_name|}}}|{{{trading_name|}}}}} | label2 = {{longitem|Native name}} | data2 = {{#if:{{{native_name|}}} | {{#if:{{{native_name_lang|}}} | {{lang|{{{native_name_lang}}}|{{{native_name}}} }} | {{{native_name}}} }} }} | label3 = {{longitem|[[Romanization|Romanized]] name}} | data3 = {{{romanized_name|}}} | label4 = Formerly | class4 = nickname | data4 = {{if empty|{{{former_names|}}}|{{{former_name|}}}}} | label5 = {{longitem|Company type}} | class5 = category | data5 = {{if empty|{{{type|}}}|{{{company_type|}}}}} | label6 = {{longitem|[[Ticker symbol|Traded as]]}} | data6 = {{{traded_as|}}} | label7 = [[International Securities Identification Number|ISIN]] | data7 = {{#invoke:WikidataIB |getValue |rank=best |P946 |name=ISIN |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{{ISIN|}}} }} | label8 = Industry | class8 = category | data8 = {{#invoke:WikidataIB |getValue |rank=best |P452 |name=industry |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{{industry|}}} }} | label9 = Genre | class9 = category | data9 = {{{genre|}}} | label10 = Predecessor{{#if:{{{predecessors|}}}|s}} | data10 = {{#invoke:WikidataIB |getValue |rank=best |P155 |name=predecessor |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{predecessors|}}}|{{{predecessor|}}}}} }} | label11 = Incorporated | data11 = {{#invoke:WikidataIB |getValue |rank=best |P1001 |name=incorporated |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{incorporated|}}}|{{{incorporation|}}}}} }} | label12 = Founded | data12 = {{#invoke:WikidataIB |getValue |rank=best |P571 |name=founded |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{founded|}}}|{{{foundation|}}}}} }} | label13 = Founder{{#if:{{{founders|}}}|s}} | class13 = agent | data13 = {{#invoke:WikidataIB |getValue |rank=best |P112 |name=founder |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{founders|}}}|{{{founder|}}}}} }} | label14 = Defunct | data14 = {{#invoke:WikidataIB |getValue |rank=best |P576 |name=defunct |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{defunct|}}}|{{{dissolved|}}}}} }} | label15 = Fate | data15 = {{{fate|}}} | label16 = Successor{{#if:{{{successors|}}}|s}} | data16 = {{#invoke:WikidataIB |getValue |rank=best |P156 |name=successor |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{successors|}}}|{{{successor|}}}}} }} | label17 = Headquarters | class17 = {{#if:{{{hq_location|}}}{{{location|}}}{{{headquarters|}}} | label | adr}} | data17 = {{#if:{{{hq_location|}}}{{{location|}}}{{{headquarters|}}}{{{hq_location_city|}}}{{{location_city|}}}{{{hq_location_country|}}}{{{location_country|}}} | {{Comma separated entries | 1 = {{if empty|{{{hq_location|}}}|{{{location|}}}|{{{headquarters|}}}}} | 2 = {{#if:{{{hq_location_city|}}}{{{location_city|}}} |<div class="ib-company-locality locality">{{#if:{{{hq_location_city|}}}{{{location_city|}}} | {{if empty|{{{hq_location_city|}}}|{{{location_city|}}}}} | }}</div>}} | 3 = {{#if:{{{hq_location_country|}}}{{{location_country|}}} |<div class="ib-company-country country-name">{{#if:{{{hq_location_country|}}}{{{location_country|}}} | {{if empty|{{{hq_location_country|}}}|{{{location_country|}}}}} | }}</div>}} }} | {{#if:{{#invoke:WikidataIB |getValue |rank=best |P159 |name=hq_location_city |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }}{{#invoke:WikidataIB |getValue |rank=best |P17 |name=hq_location_country |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }} | {{Comma separated entries | 1 = {{#if:{{#invoke:WikidataIB |getValue |rank=best |P159 |name=hq_location_city |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }} |<div class="ib-company-locality locality">{{#invoke:WikidataIB |getValue |rank=best |P159 |name=headquarters location |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }}</div>}} | 2 = {{#if:{{#invoke:WikidataIB |getValue |rank=best |P17 |name=countrry |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }} |<div class="ib-company-country country-name">{{#invoke:WikidataIB |getValue |rank=best |P17 |name=hq_location_country |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }}</div>}} }} }} }} | label19 = {{longitem|Number of locations}} | data19 = {{#if:{{{num_locations|}}}{{{locations|}}} | {{if empty|{{{num_locations|}}}|{{{locations|}}}}}{{#if:{{{num_locations_year|}}} | &nbsp;({{{num_locations_year}}}) }} }} | label20 = {{longitem|Area{{#if:{{{areas_served|}}}|s}} served}} | data20 = {{if empty|{{{areas_served|}}}|{{{area_served|}}}}} | label21 = {{longitem|Key people}} | class21 = agent | data21 = {{{key_people|}}} | label22 = Products | data22 = {{{products|}}} | label23 = {{longitem|Production output}} | data23 = {{#if:{{{production|}}} | {{{production|}}}{{#if:{{{production_year|}}} | &nbsp;({{{production_year}}}) }} }} | label24 = Brands | data24 = {{{brands|}}} | label25 = Services | class25 = category | data25 = {{{services|}}} | label26 = Revenue | data26 = {{#invoke:WikidataIB |getValue |rank=best |P2139 |name=revenue |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{revenue|}}}{{#if:{{{revenue_year|}}} | &nbsp;({{{revenue_year}}}) }} }} | label27 = {{longitem|[[Earnings before interest and taxes|Operating income]]}} | data27 = {{#invoke:WikidataIB |getValue |rank=best |P3362 |name=operating_income |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{operating_income|}}}{{#if:{{{income_year|}}} | &nbsp;({{{income_year}}}) }} }} | label28 = {{longitem|[[Net income]]}} | data28 = {{#invoke:WikidataIB |getValue |rank=best |P2295 |name=net_income |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{if empty|{{{net_income|}}}|{{{profit|}}}}}{{#if:{{{net_income_year|}}}{{{profit_year|}}} | &nbsp;({{if empty|{{{net_income_year|}}}|{{{profit_year|}}}}}) }} }} | label29 = [[Assets under management|AUM]] | data29 = {{{aum|}}} | label30 = {{nowrap|[[Asset|Total assets]]}} | data30 = {{#invoke:WikidataIB |getValue |rank=best |P2403 |name=assets |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{assets|}}}{{#if:{{{assets_year|}}} | &nbsp;({{{assets_year}}}) }} }} | label31 = {{nowrap|[[Equity (finance)|Total equity]]}} | data31 = {{#if:{{{equity|}}} | {{{equity}}}{{#if:{{{equity_year|}}} | &nbsp;({{{equity_year}}})}} }} | label32 = Owner{{#if:{{{owners|}}}|s}} | data32 = {{if empty|{{{owners|}}}|{{{owner|}}}}} | label33 = Members | data33 = {{#if:{{{members|}}} | {{{members}}}{{#if:{{{members_year|}}} | &nbsp;({{{members_year}}})}} }} | label34 = {{longitem|Number of employees}} | data34 = {{#invoke:WikidataIB |getValue |rank=best |P1128 |name=num_employees |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{num_employees|}}}{{#if:{{{num_employees_year|}}} | &nbsp;({{{num_employees_year}}}) }} }} | label35 = [[Parent company|Parent]] | data35 = {{#ifeq:{{{owners|}}}{{{owner|}}}{{{parent|}}} || {{#invoke:WikidataIB |getValue |rank=best |P749 |name=parent |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes }} | {{{parent|}}} }} | label36 = [[Division (business)|Divisions]] | data36 = {{{divisions|}}} | label37 = [[Subsidiary|Subsidiaries]] | data37 = {{if empty|{{{subsid|}}}|{{{subsidiaries|}}}}} | data39 = {{{module|}}} | label40 = [[Basel III|Capital ratio]] | data40 = {{{ratio|}}} | label41 = Rating | data41 = {{{rating|}}} | label42 = Website | data42 = {{#if:{{{website|}}}{{{homepage|}}} |{{if empty|{{{website|}}}|{{{homepage|}}}}} |{{#invoke:WikidataIB |url2 |url={{#invoke:WikidataIB |getValue |rank=best |P856 |name=website |qid={{{qid|}}} |suppressfields={{{suppressfields|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |onlysourced=yes |maxvals=1}} }} }} | below = {{#if:{{{footnotes|}}} | '''Footnotes{{\}}references'''{{break}}{{{footnotes}}} }} }}<!-- Tracking categories: -->{{main other|{{#if:{{{trading_name|}}}|[[Category:Pages using infobox company using trading name]] }}{{#ifeq:{{if empty|{{{logo|}}}|{{{company_logo|}}}|{{wikidata|property|raw|P154}}}}|{{if empty|{{{logo|}}}|{{{company_logo|}}}}}||[[Category:Pages using infobox company with a logo from wikidata]] }}{{#if:{{{image|}}}|{{#ifeq:{{#invoke:string|replace|{{{image|}}}| |_}}|{{#invoke:string|replace|{{if empty|{{{logo|}}}|{{{company_logo|}}}|{{wikidata|property|raw|P154}}}}| |_}}|[[Category:Pages using infobox company with a duplicate image]]|}}|}} }}<!-- -->{{#invoke:Check for unknown parameters|check|unknown={{main other|[[Category:Pages using infobox company with unknown parameters|_VALUE_{{PAGENAME}}]]}}|preview=Page using [[Template:Infobox company]] with unknown parameter "_VALUE_" | ignoreblank=y | alt | area_served | areas_served | assets | assets_year | aum | brands | company_logo | company_name | company_type | defunct | dissolved | divisions | embed | equity | equity_year | fate | footnotes | headquarters | former_name | former_names | foundation | founded | founder | founders | genre | homepage | hq_location | hq_location_city | hq_location_country | incorporated | image | image_alt | image_caption | image_size | image_upright | income_year | industry | ISIN | key_people | location | location_city | location_country | locations | logo | logo_alt | logo_caption | logo_class | logo_size | logo_upright | members | members_year | module | name | native_name | native_name_lang | net_income | net_income_year | num_employees | num_employees_year | num_locations | num_locations_year | operating_income | owner | owners | parent | predecessor | predecessors | production | production_year | products | profit | profit_year | rating | ratio | revenue | revenue_year | romanized_name | services | subsid | subsidiaries | successor | successors | traded_as | trade_name | trading_name | type | website| qid | fetchwikidata | suppressfields | noicon | nocat | demo | categories }}{{#invoke:Check for conflicting parameters|check|nested=1 | template = [[Template:Infobox company]] | cat = {{main other|Category:Pages using infobox company with conflicting parameters}} | logo; company_logo | logo_alt; alt | trade_name; trading_name | former_names; former_name | type; company_type | predecessors; predecessor | incorporated; incorporation | founded; foundation | founders; founder | defunct; dissolved | successors; successor | hq_location; location; headquarters | hq_location_city; location_city | hq_location_country; location_country | num_locations; locations | areas_served; area_served | net_income; profit | net_income_year; profit_year | owners; owner | subsid; subsidiaries | website; homepage }}<noinclude> {{documentation}} </noinclude> luxndm0qtjcxdvnkndyhxgz4wg5o3jn 803470 803469 2026-05-10T20:48:37Z en>Zackmann08 0 Adding deprecated parameter tracking through [[:Category:Pages using infobox company with deprecated parameters]] per [[Template_talk:Infobox_company#Deprecated_parameter_cleanup]] 803470 wikitext text/x-wiki {{Infobox | bodyclass = ib-company vcard | templatestyles = Infobox company/styles.css | child = {{lc:{{{embed}}}}} | decat = yes | titleclass = fn org | titlestyle = font-size: 125%; | title = {{#ifeq:{{lc:{{{embed}}}}} | yes | '''Company''' | {{if empty|{{{name|}}}|{{{company_name|}}}|{{PAGENAMEBASE}}}} }} | imageclass = ib-company-logo logo | image = {{#invoke:InfoboxImage |InfoboxImage |image={{#ifeq:{{lc:{{{embed}}}}} | yes | {{if empty|{{{logo|}}}|{{{company_logo|}}}}} |{{#invoke:WikidataIB |getValue |rank=best |P154 |name=logo |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=no |noicon=yes |maxvals=1 |{{if empty|{{{logo|}}}|{{{company_logo|}}}}} }} }} |size={{{logo_size|}}} |sizedefault=frameless |upright={{if empty|{{{logo_upright|}}}|1}} |alt={{if empty|{{{logo_alt|}}}|{{{alt|}}}}} |class= {{{logo_class|}}} |maxsize=250px}} | caption = {{{logo_caption|}}} | image2 = {{#invoke:InfoboxImage |InfoboxImage |image={{{image|}}} |size={{{image_size|}}} |upright={{if empty|{{{image_upright|}}}|1}} |alt={{{image_alt|}}} }} | caption2 = {{{image_caption|}}} | label1 = {{longitem|[[Trade name]]}} | data1 = {{if empty|{{{trade_name|}}}|{{{trading_name|}}}}} | label2 = {{longitem|Native name}} | data2 = {{#if:{{{native_name|}}} | {{#if:{{{native_name_lang|}}} | {{lang|{{{native_name_lang}}}|{{{native_name}}} }} | {{{native_name}}} }} }} | label3 = {{longitem|[[Romanization|Romanized]] name}} | data3 = {{{romanized_name|}}} | label4 = Formerly | class4 = nickname | data4 = {{if empty|{{{former_names|}}}|{{{former_name|}}}}} | label5 = {{longitem|Company type}} | class5 = category | data5 = {{if empty|{{{type|}}}|{{{company_type|}}}}} | label6 = {{longitem|[[Ticker symbol|Traded as]]}} | data6 = {{{traded_as|}}} | label7 = [[International Securities Identification Number|ISIN]] | data7 = {{#invoke:WikidataIB |getValue |rank=best |P946 |name=ISIN |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{{ISIN|}}} }} | label8 = Industry | class8 = category | data8 = {{#invoke:WikidataIB |getValue |rank=best |P452 |name=industry |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{{industry|}}} }} | label9 = Genre | class9 = category | data9 = {{{genre|}}} | label10 = Predecessor{{#if:{{{predecessors|}}}|s}} | data10 = {{#invoke:WikidataIB |getValue |rank=best |P155 |name=predecessor |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{predecessors|}}}|{{{predecessor|}}}}} }} | label11 = Incorporated | data11 = {{#invoke:WikidataIB |getValue |rank=best |P1001 |name=incorporated |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{incorporated|}}}|{{{incorporation|}}}}} }} | label12 = Founded | data12 = {{#invoke:WikidataIB |getValue |rank=best |P571 |name=founded |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{founded|}}}|{{{foundation|}}}}} }} | label13 = Founder{{#if:{{{founders|}}}|s}} | class13 = agent | data13 = {{#invoke:WikidataIB |getValue |rank=best |P112 |name=founder |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{founders|}}}|{{{founder|}}}}} }} | label14 = Defunct | data14 = {{#invoke:WikidataIB |getValue |rank=best |P576 |name=defunct |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{defunct|}}}|{{{dissolved|}}}}} }} | label15 = Fate | data15 = {{{fate|}}} | label16 = Successor{{#if:{{{successors|}}}|s}} | data16 = {{#invoke:WikidataIB |getValue |rank=best |P156 |name=successor |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{successors|}}}|{{{successor|}}}}} }} | label17 = Headquarters | class17 = {{#if:{{{hq_location|}}}{{{location|}}}{{{headquarters|}}} | label | adr}} | data17 = {{#if:{{{hq_location|}}}{{{location|}}}{{{headquarters|}}}{{{hq_location_city|}}}{{{location_city|}}}{{{hq_location_country|}}}{{{location_country|}}} | {{Comma separated entries | 1 = {{if empty|{{{hq_location|}}}|{{{location|}}}|{{{headquarters|}}}}} | 2 = {{#if:{{{hq_location_city|}}}{{{location_city|}}} |<div class="ib-company-locality locality">{{#if:{{{hq_location_city|}}}{{{location_city|}}} | {{if empty|{{{hq_location_city|}}}|{{{location_city|}}}}} | }}</div>}} | 3 = {{#if:{{{hq_location_country|}}}{{{location_country|}}} |<div class="ib-company-country country-name">{{#if:{{{hq_location_country|}}}{{{location_country|}}} | {{if empty|{{{hq_location_country|}}}|{{{location_country|}}}}} | }}</div>}} }} | {{#if:{{#invoke:WikidataIB |getValue |rank=best |P159 |name=hq_location_city |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }}{{#invoke:WikidataIB |getValue |rank=best |P17 |name=hq_location_country |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }} | {{Comma separated entries | 1 = {{#if:{{#invoke:WikidataIB |getValue |rank=best |P159 |name=hq_location_city |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }} |<div class="ib-company-locality locality">{{#invoke:WikidataIB |getValue |rank=best |P159 |name=headquarters location |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }}</div>}} | 2 = {{#if:{{#invoke:WikidataIB |getValue |rank=best |P17 |name=countrry |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }} |<div class="ib-company-country country-name">{{#invoke:WikidataIB |getValue |rank=best |P17 |name=hq_location_country |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }}</div>}} }} }} }} | label19 = {{longitem|Number of locations}} | data19 = {{#if:{{{num_locations|}}}{{{locations|}}} | {{if empty|{{{num_locations|}}}|{{{locations|}}}}}{{#if:{{{num_locations_year|}}} | &nbsp;({{{num_locations_year}}}) }} }} | label20 = {{longitem|Area{{#if:{{{areas_served|}}}|s}} served}} | data20 = {{if empty|{{{areas_served|}}}|{{{area_served|}}}}} | label21 = {{longitem|Key people}} | class21 = agent | data21 = {{{key_people|}}} | label22 = Products | data22 = {{{products|}}} | label23 = {{longitem|Production output}} | data23 = {{#if:{{{production|}}} | {{{production|}}}{{#if:{{{production_year|}}} | &nbsp;({{{production_year}}}) }} }} | label24 = Brands | data24 = {{{brands|}}} | label25 = Services | class25 = category | data25 = {{{services|}}} | label26 = Revenue | data26 = {{#invoke:WikidataIB |getValue |rank=best |P2139 |name=revenue |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{revenue|}}}{{#if:{{{revenue_year|}}} | &nbsp;({{{revenue_year}}}) }} }} | label27 = {{longitem|[[Earnings before interest and taxes|Operating income]]}} | data27 = {{#invoke:WikidataIB |getValue |rank=best |P3362 |name=operating_income |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{operating_income|}}}{{#if:{{{income_year|}}} | &nbsp;({{{income_year}}}) }} }} | label28 = {{longitem|[[Net income]]}} | data28 = {{#invoke:WikidataIB |getValue |rank=best |P2295 |name=net_income |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{if empty|{{{net_income|}}}|{{{profit|}}}}}{{#if:{{{net_income_year|}}}{{{profit_year|}}} | &nbsp;({{if empty|{{{net_income_year|}}}|{{{profit_year|}}}}}) }} }} | label29 = [[Assets under management|AUM]] | data29 = {{{aum|}}} | label30 = {{nowrap|[[Asset|Total assets]]}} | data30 = {{#invoke:WikidataIB |getValue |rank=best |P2403 |name=assets |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{assets|}}}{{#if:{{{assets_year|}}} | &nbsp;({{{assets_year}}}) }} }} | label31 = {{nowrap|[[Equity (finance)|Total equity]]}} | data31 = {{#if:{{{equity|}}} | {{{equity}}}{{#if:{{{equity_year|}}} | &nbsp;({{{equity_year}}})}} }} | label32 = Owner{{#if:{{{owners|}}}|s}} | data32 = {{if empty|{{{owners|}}}|{{{owner|}}}}} | label33 = Members | data33 = {{#if:{{{members|}}}{{{num_members|}}} | {{if empty|{{{members|}}}|{{{num_members|}}}}}{{#if:{{{members_year|}}}{{{num_members_year|}}} | &nbsp;({{if empty|{{{members_year|}}}|{{{num_members_year|}}}}})}} }} | label34 = {{longitem|Number of employees}} | data34 = {{#invoke:WikidataIB |getValue |rank=best |P1128 |name=num_employees |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{num_employees|}}}{{#if:{{{num_employees_year|}}} | &nbsp;({{{num_employees_year}}}) }} }} | label35 = [[Parent company|Parent]] | data35 = {{#ifeq:{{{owners|}}}{{{owner|}}}{{{parent|}}} || {{#invoke:WikidataIB |getValue |rank=best |P749 |name=parent |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes }} | {{{parent|}}} }} | label36 = [[Division (business)|Divisions]] | data36 = {{{divisions|}}} | label37 = [[Subsidiary|Subsidiaries]] | data37 = {{if empty|{{{subsid|}}}|{{{subsidiaries|}}}}} | data39 = {{{module|}}} | label40 = [[Basel III|Capital ratio]] | data40 = {{{ratio|}}} | label41 = Rating | data41 = {{{rating|}}} | label42 = Website | data42 = {{#if:{{{website|}}}{{{homepage|}}} |{{if empty|{{{website|}}}|{{{homepage|}}}}} |{{#invoke:WikidataIB |url2 |url={{#invoke:WikidataIB |getValue |rank=best |P856 |name=website |qid={{{qid|}}} |suppressfields={{{suppressfields|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |onlysourced=yes |maxvals=1}} }} }} | below = {{#if:{{{footnotes|}}} | '''Footnotes{{\}}references'''{{break}}{{{footnotes}}} }} }}<!-- Tracking categories: -->{{main other|{{#if:{{{trading_name|}}}|[[Category:Pages using infobox company using trading name]] }}{{#ifeq:{{if empty|{{{logo|}}}|{{{company_logo|}}}|{{wikidata|property|raw|P154}}}}|{{if empty|{{{logo|}}}|{{{company_logo|}}}}}||[[Category:Pages using infobox company with a logo from wikidata]] }}{{#if:{{{image|}}}|{{#ifeq:{{#invoke:string|replace|{{{image|}}}| |_}}|{{#invoke:string|replace|{{if empty|{{{logo|}}}|{{{company_logo|}}}|{{wikidata|property|raw|P154}}}}| |_}}|[[Category:Pages using infobox company with a duplicate image]]|}}|}} }}<!-- -->{{#invoke:Check for unknown parameters|check|unknown={{main other|[[Category:Pages using infobox company with unknown parameters|_VALUE_{{PAGENAME}}]]}}|preview=Page using [[Template:Infobox company]] with unknown parameter "_VALUE_" | ignoreblank=y | alt | area_served | areas_served | assets | assets_year | aum | brands | company_logo | company_name | company_type | defunct | dissolved | divisions | embed | equity | equity_year | fate | footnotes | headquarters | former_name | former_names | foundation | founded | founder | founders | genre | homepage | hq_location | hq_location_city | hq_location_country | incorporated | image | image_alt | image_caption | image_size | image_upright | income_year | industry | ISIN | key_people | location | location_city | location_country | locations | logo | logo_alt | logo_caption | logo_class | logo_size | logo_upright | members | members_year | module | name | native_name | native_name_lang | net_income | net_income_year | num_employees | num_employees_year | num_locations | num_locations_year | operating_income | owner | owners | parent | predecessor | predecessors | production | production_year | products | profit | profit_year | rating | ratio | revenue | revenue_year | romanized_name | services | subsid | subsidiaries | successor | successors | traded_as | trade_name | trading_name | type | website| qid | fetchwikidata | suppressfields | noicon | nocat | demo | categories | num_members | num_members_year }}{{#invoke:Check for conflicting parameters|check|nested=1 | template = [[Template:Infobox company]] | cat = {{main other|Category:Pages using infobox company with conflicting parameters}} | logo; company_logo | logo_alt; alt | trade_name; trading_name | former_names; former_name | type; company_type | predecessors; predecessor | incorporated; incorporation | founded; foundation | founders; founder | defunct; dissolved | successors; successor | hq_location; location; headquarters | hq_location_city; location_city | hq_location_country; location_country | num_locations; locations | areas_served; area_served | net_income; profit | net_income_year; profit_year | owners; owner | subsid; subsidiaries | website; homepage }}{{#invoke:Check for deprecated parameters|check | _category = [[Category:Pages using infobox company with deprecated parameters|_VALUE_]] | company_name = name | company_logo = logo | alt = logo_alt | trading_name = trade_name | company_type = type | incorporation = incorporated | dissolved = defunct | location = hq_location | headquarters = hq_location | location_city = hq_location_city | location_country = hq_location_country | locations = num_locations | members = num_members | members_year = num_members_year | profit = net_income | profit_year = net_income_year | subsid = subsidiaries | foundation = founded | homepage = website }}<noinclude> {{documentation}} </noinclude> sjbgzgx1abke1n3ihdcy0nc7i5rk7o1 803471 803470 2026-05-10T21:38:21Z en>Zackmann08 0 Clarify categorization of non-articles 803471 wikitext text/x-wiki {{Infobox | bodyclass = ib-company vcard | templatestyles = Infobox company/styles.css | child = {{lc:{{{embed}}}}} | decat = yes | titleclass = fn org | titlestyle = font-size: 125%; | title = {{#ifeq:{{lc:{{{embed}}}}} | yes | '''Company''' | {{if empty|{{{name|}}}|{{{company_name|}}}|{{PAGENAMEBASE}}}} }} | imageclass = ib-company-logo logo | image = {{#invoke:InfoboxImage |InfoboxImage |image={{#ifeq:{{lc:{{{embed}}}}} | yes | {{if empty|{{{logo|}}}|{{{company_logo|}}}}} |{{#invoke:WikidataIB |getValue |rank=best |P154 |name=logo |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=no |noicon=yes |maxvals=1 |{{if empty|{{{logo|}}}|{{{company_logo|}}}}} }} }} |size={{{logo_size|}}} |sizedefault=frameless |upright={{if empty|{{{logo_upright|}}}|1}} |alt={{if empty|{{{logo_alt|}}}|{{{alt|}}}}} |class= {{{logo_class|}}} |maxsize=250px}} | caption = {{{logo_caption|}}} | image2 = {{#invoke:InfoboxImage |InfoboxImage |image={{{image|}}} |size={{{image_size|}}} |upright={{if empty|{{{image_upright|}}}|1}} |alt={{{image_alt|}}} }} | caption2 = {{{image_caption|}}} | label1 = {{longitem|[[Trade name]]}} | data1 = {{if empty|{{{trade_name|}}}|{{{trading_name|}}}}} | label2 = {{longitem|Native name}} | data2 = {{#if:{{{native_name|}}} | {{#if:{{{native_name_lang|}}} | {{lang|{{{native_name_lang}}}|{{{native_name}}} }} | {{{native_name}}} }} }} | label3 = {{longitem|[[Romanization|Romanized]] name}} | data3 = {{{romanized_name|}}} | label4 = Formerly | class4 = nickname | data4 = {{if empty|{{{former_names|}}}|{{{former_name|}}}}} | label5 = {{longitem|Company type}} | class5 = category | data5 = {{if empty|{{{type|}}}|{{{company_type|}}}}} | label6 = {{longitem|[[Ticker symbol|Traded as]]}} | data6 = {{{traded_as|}}} | label7 = [[International Securities Identification Number|ISIN]] | data7 = {{#invoke:WikidataIB |getValue |rank=best |P946 |name=ISIN |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{{ISIN|}}} }} | label8 = Industry | class8 = category | data8 = {{#invoke:WikidataIB |getValue |rank=best |P452 |name=industry |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{{industry|}}} }} | label9 = Genre | class9 = category | data9 = {{{genre|}}} | label10 = Predecessor{{#if:{{{predecessors|}}}|s}} | data10 = {{#invoke:WikidataIB |getValue |rank=best |P155 |name=predecessor |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{predecessors|}}}|{{{predecessor|}}}}} }} | label11 = Incorporated | data11 = {{#invoke:WikidataIB |getValue |rank=best |P1001 |name=incorporated |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{incorporated|}}}|{{{incorporation|}}}}} }} | label12 = Founded | data12 = {{#invoke:WikidataIB |getValue |rank=best |P571 |name=founded |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{founded|}}}|{{{foundation|}}}}} }} | label13 = Founder{{#if:{{{founders|}}}|s}} | class13 = agent | data13 = {{#invoke:WikidataIB |getValue |rank=best |P112 |name=founder |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{founders|}}}|{{{founder|}}}}} }} | label14 = Defunct | data14 = {{#invoke:WikidataIB |getValue |rank=best |P576 |name=defunct |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{defunct|}}}|{{{dissolved|}}}}} }} | label15 = Fate | data15 = {{{fate|}}} | label16 = Successor{{#if:{{{successors|}}}|s}} | data16 = {{#invoke:WikidataIB |getValue |rank=best |P156 |name=successor |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{successors|}}}|{{{successor|}}}}} }} | label17 = Headquarters | class17 = {{#if:{{{hq_location|}}}{{{location|}}}{{{headquarters|}}} | label | adr}} | data17 = {{#if:{{{hq_location|}}}{{{location|}}}{{{headquarters|}}}{{{hq_location_city|}}}{{{location_city|}}}{{{hq_location_country|}}}{{{location_country|}}} | {{Comma separated entries | 1 = {{if empty|{{{hq_location|}}}|{{{location|}}}|{{{headquarters|}}}}} | 2 = {{#if:{{{hq_location_city|}}}{{{location_city|}}} |<div class="ib-company-locality locality">{{#if:{{{hq_location_city|}}}{{{location_city|}}} | {{if empty|{{{hq_location_city|}}}|{{{location_city|}}}}} | }}</div>}} | 3 = {{#if:{{{hq_location_country|}}}{{{location_country|}}} |<div class="ib-company-country country-name">{{#if:{{{hq_location_country|}}}{{{location_country|}}} | {{if empty|{{{hq_location_country|}}}|{{{location_country|}}}}} | }}</div>}} }} | {{#if:{{#invoke:WikidataIB |getValue |rank=best |P159 |name=hq_location_city |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }}{{#invoke:WikidataIB |getValue |rank=best |P17 |name=hq_location_country |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }} | {{Comma separated entries | 1 = {{#if:{{#invoke:WikidataIB |getValue |rank=best |P159 |name=hq_location_city |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }} |<div class="ib-company-locality locality">{{#invoke:WikidataIB |getValue |rank=best |P159 |name=headquarters location |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }}</div>}} | 2 = {{#if:{{#invoke:WikidataIB |getValue |rank=best |P17 |name=countrry |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }} |<div class="ib-company-country country-name">{{#invoke:WikidataIB |getValue |rank=best |P17 |name=hq_location_country |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }}</div>}} }} }} }} | label19 = {{longitem|Number of locations}} | data19 = {{#if:{{{num_locations|}}}{{{locations|}}} | {{if empty|{{{num_locations|}}}|{{{locations|}}}}}{{#if:{{{num_locations_year|}}} | &nbsp;({{{num_locations_year}}}) }} }} | label20 = {{longitem|Area{{#if:{{{areas_served|}}}|s}} served}} | data20 = {{if empty|{{{areas_served|}}}|{{{area_served|}}}}} | label21 = {{longitem|Key people}} | class21 = agent | data21 = {{{key_people|}}} | label22 = Products | data22 = {{{products|}}} | label23 = {{longitem|Production output}} | data23 = {{#if:{{{production|}}} | {{{production|}}}{{#if:{{{production_year|}}} | &nbsp;({{{production_year}}}) }} }} | label24 = Brands | data24 = {{{brands|}}} | label25 = Services | class25 = category | data25 = {{{services|}}} | label26 = Revenue | data26 = {{#invoke:WikidataIB |getValue |rank=best |P2139 |name=revenue |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{revenue|}}}{{#if:{{{revenue_year|}}} | &nbsp;({{{revenue_year}}}) }} }} | label27 = {{longitem|[[Earnings before interest and taxes|Operating income]]}} | data27 = {{#invoke:WikidataIB |getValue |rank=best |P3362 |name=operating_income |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{operating_income|}}}{{#if:{{{income_year|}}} | &nbsp;({{{income_year}}}) }} }} | label28 = {{longitem|[[Net income]]}} | data28 = {{#invoke:WikidataIB |getValue |rank=best |P2295 |name=net_income |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{if empty|{{{net_income|}}}|{{{profit|}}}}}{{#if:{{{net_income_year|}}}{{{profit_year|}}} | &nbsp;({{if empty|{{{net_income_year|}}}|{{{profit_year|}}}}}) }} }} | label29 = [[Assets under management|AUM]] | data29 = {{{aum|}}} | label30 = {{nowrap|[[Asset|Total assets]]}} | data30 = {{#invoke:WikidataIB |getValue |rank=best |P2403 |name=assets |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{assets|}}}{{#if:{{{assets_year|}}} | &nbsp;({{{assets_year}}}) }} }} | label31 = {{nowrap|[[Equity (finance)|Total equity]]}} | data31 = {{#if:{{{equity|}}} | {{{equity}}}{{#if:{{{equity_year|}}} | &nbsp;({{{equity_year}}})}} }} | label32 = Owner{{#if:{{{owners|}}}|s}} | data32 = {{if empty|{{{owners|}}}|{{{owner|}}}}} | label33 = Members | data33 = {{#if:{{{members|}}}{{{num_members|}}} | {{if empty|{{{members|}}}|{{{num_members|}}}}}{{#if:{{{members_year|}}}{{{num_members_year|}}} | &nbsp;({{if empty|{{{members_year|}}}|{{{num_members_year|}}}}})}} }} | label34 = {{longitem|Number of employees}} | data34 = {{#invoke:WikidataIB |getValue |rank=best |P1128 |name=num_employees |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{num_employees|}}}{{#if:{{{num_employees_year|}}} | &nbsp;({{{num_employees_year}}}) }} }} | label35 = [[Parent company|Parent]] | data35 = {{#ifeq:{{{owners|}}}{{{owner|}}}{{{parent|}}} || {{#invoke:WikidataIB |getValue |rank=best |P749 |name=parent |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes }} | {{{parent|}}} }} | label36 = [[Division (business)|Divisions]] | data36 = {{{divisions|}}} | label37 = [[Subsidiary|Subsidiaries]] | data37 = {{if empty|{{{subsid|}}}|{{{subsidiaries|}}}}} | data39 = {{{module|}}} | label40 = [[Basel III|Capital ratio]] | data40 = {{{ratio|}}} | label41 = Rating | data41 = {{{rating|}}} | label42 = Website | data42 = {{#if:{{{website|}}}{{{homepage|}}} |{{if empty|{{{website|}}}|{{{homepage|}}}}} |{{#invoke:WikidataIB |url2 |url={{#invoke:WikidataIB |getValue |rank=best |P856 |name=website |qid={{{qid|}}} |suppressfields={{{suppressfields|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |onlysourced=yes |maxvals=1}} }} }} | below = {{#if:{{{footnotes|}}} | '''Footnotes{{\}}references'''{{break}}{{{footnotes}}} }} }}<!-- Tracking categories: -->{{main other|{{#if:{{{trading_name|}}}|[[Category:Pages using infobox company using trading name]] }}{{#ifeq:{{if empty|{{{logo|}}}|{{{company_logo|}}}|{{wikidata|property|raw|P154}}}}|{{if empty|{{{logo|}}}|{{{company_logo|}}}}}||[[Category:Pages using infobox company with a logo from wikidata]] }}{{#if:{{{image|}}}|{{#ifeq:{{#invoke:string|replace|{{{image|}}}| |_}}|{{#invoke:string|replace|{{if empty|{{{logo|}}}|{{{company_logo|}}}|{{wikidata|property|raw|P154}}}}| |_}}|[[Category:Pages using infobox company with a duplicate image]]|}}|}} }}<!-- -->{{#invoke:Check for unknown parameters|check|unknown={{main other|[[Category:Pages using infobox company with unknown parameters|_VALUE_{{PAGENAME}}]]}}|preview=Page using [[Template:Infobox company]] with unknown parameter "_VALUE_" | ignoreblank=y | alt | area_served | areas_served | assets | assets_year | aum | brands | company_logo | company_name | company_type | defunct | dissolved | divisions | embed | equity | equity_year | fate | footnotes | headquarters | former_name | former_names | foundation | founded | founder | founders | genre | homepage | hq_location | hq_location_city | hq_location_country | incorporated | image | image_alt | image_caption | image_size | image_upright | income_year | industry | ISIN | key_people | location | location_city | location_country | locations | logo | logo_alt | logo_caption | logo_class | logo_size | logo_upright | members | members_year | module | name | native_name | native_name_lang | net_income | net_income_year | num_employees | num_employees_year | num_locations | num_locations_year | operating_income | owner | owners | parent | predecessor | predecessors | production | production_year | products | profit | profit_year | rating | ratio | revenue | revenue_year | romanized_name | services | subsid | subsidiaries | successor | successors | traded_as | trade_name | trading_name | type | website| qid | fetchwikidata | suppressfields | noicon | nocat | demo | categories | num_members | num_members_year }}{{#invoke:Check for conflicting parameters|check|nested=1 | template = [[Template:Infobox company]] | cat = {{main other|Category:Pages using infobox company with conflicting parameters}} | logo; company_logo | logo_alt; alt | trade_name; trading_name | former_names; former_name | type; company_type | predecessors; predecessor | incorporated; incorporation | founded; foundation | founders; founder | defunct; dissolved | successors; successor | hq_location; location; headquarters | hq_location_city; location_city | hq_location_country; location_country | num_locations; locations | areas_served; area_served | net_income; profit | net_income_year; profit_year | owners; owner | subsid; subsidiaries | website; homepage }}{{#invoke:Check for deprecated parameters|check | _category = {{main other|[[Category:Pages using infobox company with deprecated parameters|_VALUE_]]|[[Category:Pages using infobox company with deprecated parameters|§]]}} | company_name = name | company_logo = logo | alt = logo_alt | trading_name = trade_name | company_type = type | incorporation = incorporated | dissolved = defunct | location = hq_location | headquarters = hq_location | location_city = hq_location_city | location_country = hq_location_country | locations = num_locations | members = num_members | members_year = num_members_year | profit = net_income | profit_year = net_income_year | subsid = subsidiaries | foundation = founded | homepage = website }}<noinclude> {{documentation}} </noinclude> 2zgnaa0ljrq224xd8nhfre8787gnylq 803472 803471 2026-05-23T06:20:15Z en>Zackmann08 0 No need to include all these user/draft articles. I regret this decision.. 803472 wikitext text/x-wiki {{Infobox | bodyclass = ib-company vcard | templatestyles = Infobox company/styles.css | child = {{lc:{{{embed}}}}} | decat = yes | titleclass = fn org | titlestyle = font-size: 125%; | title = {{#ifeq:{{lc:{{{embed}}}}} | yes | '''Company''' | {{if empty|{{{name|}}}|{{{company_name|}}}|{{PAGENAMEBASE}}}} }} | imageclass = ib-company-logo logo | image = {{#invoke:InfoboxImage |InfoboxImage |image={{#ifeq:{{lc:{{{embed}}}}} | yes | {{if empty|{{{logo|}}}|{{{company_logo|}}}}} |{{#invoke:WikidataIB |getValue |rank=best |P154 |name=logo |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=no |noicon=yes |maxvals=1 |{{if empty|{{{logo|}}}|{{{company_logo|}}}}} }} }} |size={{{logo_size|}}} |sizedefault=frameless |upright={{if empty|{{{logo_upright|}}}|1}} |alt={{if empty|{{{logo_alt|}}}|{{{alt|}}}}} |class= {{{logo_class|}}} |maxsize=250px}} | caption = {{{logo_caption|}}} | image2 = {{#invoke:InfoboxImage |InfoboxImage |image={{{image|}}} |size={{{image_size|}}} |upright={{if empty|{{{image_upright|}}}|1}} |alt={{{image_alt|}}} }} | caption2 = {{{image_caption|}}} | label1 = {{longitem|[[Trade name]]}} | data1 = {{if empty|{{{trade_name|}}}|{{{trading_name|}}}}} | label2 = {{longitem|Native name}} | data2 = {{#if:{{{native_name|}}} | {{#if:{{{native_name_lang|}}} | {{lang|{{{native_name_lang}}}|{{{native_name}}} }} | {{{native_name}}} }} }} | label3 = {{longitem|[[Romanization|Romanized]] name}} | data3 = {{{romanized_name|}}} | label4 = Formerly | class4 = nickname | data4 = {{if empty|{{{former_names|}}}|{{{former_name|}}}}} | label5 = {{longitem|Company type}} | class5 = category | data5 = {{if empty|{{{type|}}}|{{{company_type|}}}}} | label6 = {{longitem|[[Ticker symbol|Traded as]]}} | data6 = {{{traded_as|}}} | label7 = [[International Securities Identification Number|ISIN]] | data7 = {{#invoke:WikidataIB |getValue |rank=best |P946 |name=ISIN |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{{ISIN|}}} }} | label8 = Industry | class8 = category | data8 = {{#invoke:WikidataIB |getValue |rank=best |P452 |name=industry |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{{industry|}}} }} | label9 = Genre | class9 = category | data9 = {{{genre|}}} | label10 = Predecessor{{#if:{{{predecessors|}}}|s}} | data10 = {{#invoke:WikidataIB |getValue |rank=best |P155 |name=predecessor |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{predecessors|}}}|{{{predecessor|}}}}} }} | label11 = Incorporated | data11 = {{#invoke:WikidataIB |getValue |rank=best |P1001 |name=incorporated |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{incorporated|}}}|{{{incorporation|}}}}} }} | label12 = Founded | data12 = {{#invoke:WikidataIB |getValue |rank=best |P571 |name=founded |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{founded|}}}|{{{foundation|}}}}} }} | label13 = Founder{{#if:{{{founders|}}}|s}} | class13 = agent | data13 = {{#invoke:WikidataIB |getValue |rank=best |P112 |name=founder |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{founders|}}}|{{{founder|}}}}} }} | label14 = Defunct | data14 = {{#invoke:WikidataIB |getValue |rank=best |P576 |name=defunct |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{defunct|}}}|{{{dissolved|}}}}} }} | label15 = Fate | data15 = {{{fate|}}} | label16 = Successor{{#if:{{{successors|}}}|s}} | data16 = {{#invoke:WikidataIB |getValue |rank=best |P156 |name=successor |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{successors|}}}|{{{successor|}}}}} }} | label17 = Headquarters | class17 = {{#if:{{{hq_location|}}}{{{location|}}}{{{headquarters|}}} | label | adr}} | data17 = {{#if:{{{hq_location|}}}{{{location|}}}{{{headquarters|}}}{{{hq_location_city|}}}{{{location_city|}}}{{{hq_location_country|}}}{{{location_country|}}} | {{Comma separated entries | 1 = {{if empty|{{{hq_location|}}}|{{{location|}}}|{{{headquarters|}}}}} | 2 = {{#if:{{{hq_location_city|}}}{{{location_city|}}} |<div class="ib-company-locality locality">{{#if:{{{hq_location_city|}}}{{{location_city|}}} | {{if empty|{{{hq_location_city|}}}|{{{location_city|}}}}} | }}</div>}} | 3 = {{#if:{{{hq_location_country|}}}{{{location_country|}}} |<div class="ib-company-country country-name">{{#if:{{{hq_location_country|}}}{{{location_country|}}} | {{if empty|{{{hq_location_country|}}}|{{{location_country|}}}}} | }}</div>}} }} | {{#if:{{#invoke:WikidataIB |getValue |rank=best |P159 |name=hq_location_city |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }}{{#invoke:WikidataIB |getValue |rank=best |P17 |name=hq_location_country |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }} | {{Comma separated entries | 1 = {{#if:{{#invoke:WikidataIB |getValue |rank=best |P159 |name=hq_location_city |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }} |<div class="ib-company-locality locality">{{#invoke:WikidataIB |getValue |rank=best |P159 |name=headquarters location |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }}</div>}} | 2 = {{#if:{{#invoke:WikidataIB |getValue |rank=best |P17 |name=countrry |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }} |<div class="ib-company-country country-name">{{#invoke:WikidataIB |getValue |rank=best |P17 |name=hq_location_country |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }}</div>}} }} }} }} | label19 = {{longitem|Number of locations}} | data19 = {{#if:{{{num_locations|}}}{{{locations|}}} | {{if empty|{{{num_locations|}}}|{{{locations|}}}}}{{#if:{{{num_locations_year|}}} | &nbsp;({{{num_locations_year}}}) }} }} | label20 = {{longitem|Area{{#if:{{{areas_served|}}}|s}} served}} | data20 = {{if empty|{{{areas_served|}}}|{{{area_served|}}}}} | label21 = {{longitem|Key people}} | class21 = agent | data21 = {{{key_people|}}} | label22 = Products | data22 = {{{products|}}} | label23 = {{longitem|Production output}} | data23 = {{#if:{{{production|}}} | {{{production|}}}{{#if:{{{production_year|}}} | &nbsp;({{{production_year}}}) }} }} | label24 = Brands | data24 = {{{brands|}}} | label25 = Services | class25 = category | data25 = {{{services|}}} | label26 = Revenue | data26 = {{#invoke:WikidataIB |getValue |rank=best |P2139 |name=revenue |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{revenue|}}}{{#if:{{{revenue_year|}}} | &nbsp;({{{revenue_year}}}) }} }} | label27 = {{longitem|[[Earnings before interest and taxes|Operating income]]}} | data27 = {{#invoke:WikidataIB |getValue |rank=best |P3362 |name=operating_income |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{operating_income|}}}{{#if:{{{income_year|}}} | &nbsp;({{{income_year}}}) }} }} | label28 = {{longitem|[[Net income]]}} | data28 = {{#invoke:WikidataIB |getValue |rank=best |P2295 |name=net_income |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{if empty|{{{net_income|}}}|{{{profit|}}}}}{{#if:{{{net_income_year|}}}{{{profit_year|}}} | &nbsp;({{if empty|{{{net_income_year|}}}|{{{profit_year|}}}}}) }} }} | label29 = [[Assets under management|AUM]] | data29 = {{{aum|}}} | label30 = {{nowrap|[[Asset|Total assets]]}} | data30 = {{#invoke:WikidataIB |getValue |rank=best |P2403 |name=assets |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{assets|}}}{{#if:{{{assets_year|}}} | &nbsp;({{{assets_year}}}) }} }} | label31 = {{nowrap|[[Equity (finance)|Total equity]]}} | data31 = {{#if:{{{equity|}}} | {{{equity}}}{{#if:{{{equity_year|}}} | &nbsp;({{{equity_year}}})}} }} | label32 = Owner{{#if:{{{owners|}}}|s}} | data32 = {{if empty|{{{owners|}}}|{{{owner|}}}}} | label33 = Members | data33 = {{#if:{{{members|}}}{{{num_members|}}} | {{if empty|{{{members|}}}|{{{num_members|}}}}}{{#if:{{{members_year|}}}{{{num_members_year|}}} | &nbsp;({{if empty|{{{members_year|}}}|{{{num_members_year|}}}}})}} }} | label34 = {{longitem|Number of employees}} | data34 = {{#invoke:WikidataIB |getValue |rank=best |P1128 |name=num_employees |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{num_employees|}}}{{#if:{{{num_employees_year|}}} | &nbsp;({{{num_employees_year}}}) }} }} | label35 = [[Parent company|Parent]] | data35 = {{#ifeq:{{{owners|}}}{{{owner|}}}{{{parent|}}} || {{#invoke:WikidataIB |getValue |rank=best |P749 |name=parent |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes }} | {{{parent|}}} }} | label36 = [[Division (business)|Divisions]] | data36 = {{{divisions|}}} | label37 = [[Subsidiary|Subsidiaries]] | data37 = {{if empty|{{{subsid|}}}|{{{subsidiaries|}}}}} | data39 = {{{module|}}} | label40 = [[Basel III|Capital ratio]] | data40 = {{{ratio|}}} | label41 = Rating | data41 = {{{rating|}}} | label42 = Website | data42 = {{#if:{{{website|}}}{{{homepage|}}} |{{if empty|{{{website|}}}|{{{homepage|}}}}} |{{#invoke:WikidataIB |url2 |url={{#invoke:WikidataIB |getValue |rank=best |P856 |name=website |qid={{{qid|}}} |suppressfields={{{suppressfields|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |onlysourced=yes |maxvals=1}} }} }} | below = {{#if:{{{footnotes|}}} | '''Footnotes{{\}}references'''{{break}}{{{footnotes}}} }} }}<!-- Tracking categories: -->{{main other|{{#if:{{{trading_name|}}}|[[Category:Pages using infobox company using trading name]] }}{{#ifeq:{{if empty|{{{logo|}}}|{{{company_logo|}}}|{{wikidata|property|raw|P154}}}}|{{if empty|{{{logo|}}}|{{{company_logo|}}}}}||[[Category:Pages using infobox company with a logo from wikidata]] }}{{#if:{{{image|}}}|{{#ifeq:{{#invoke:string|replace|{{{image|}}}| |_}}|{{#invoke:string|replace|{{if empty|{{{logo|}}}|{{{company_logo|}}}|{{wikidata|property|raw|P154}}}}| |_}}|[[Category:Pages using infobox company with a duplicate image]]|}}|}} }}<!-- -->{{#invoke:Check for unknown parameters|check|unknown={{main other|[[Category:Pages using infobox company with unknown parameters|_VALUE_{{PAGENAME}}]]}}|preview=Page using [[Template:Infobox company]] with unknown parameter "_VALUE_" | ignoreblank=y | alt | area_served | areas_served | assets | assets_year | aum | brands | company_logo | company_name | company_type | defunct | dissolved | divisions | embed | equity | equity_year | fate | footnotes | headquarters | former_name | former_names | foundation | founded | founder | founders | genre | homepage | hq_location | hq_location_city | hq_location_country | incorporated | image | image_alt | image_caption | image_size | image_upright | income_year | industry | ISIN | key_people | location | location_city | location_country | locations | logo | logo_alt | logo_caption | logo_class | logo_size | logo_upright | members | members_year | module | name | native_name | native_name_lang | net_income | net_income_year | num_employees | num_employees_year | num_locations | num_locations_year | operating_income | owner | owners | parent | predecessor | predecessors | production | production_year | products | profit | profit_year | rating | ratio | revenue | revenue_year | romanized_name | services | subsid | subsidiaries | successor | successors | traded_as | trade_name | trading_name | type | website| qid | fetchwikidata | suppressfields | noicon | nocat | demo | categories | num_members | num_members_year }}{{#invoke:Check for conflicting parameters|check|nested=1 | template = [[Template:Infobox company]] | cat = {{main other|Category:Pages using infobox company with conflicting parameters}} | logo; company_logo | logo_alt; alt | trade_name; trading_name | former_names; former_name | type; company_type | predecessors; predecessor | incorporated; incorporation | founded; foundation | founders; founder | defunct; dissolved | successors; successor | hq_location; location; headquarters | hq_location_city; location_city | hq_location_country; location_country | num_locations; locations | areas_served; area_served | net_income; profit | net_income_year; profit_year | owners; owner | subsid; subsidiaries | website; homepage }}{{#invoke:Check for deprecated parameters|check | _category = {{main other|[[Category:Pages using infobox company with deprecated parameters|_VALUE_]]}} | company_name = name | company_logo = logo | alt = logo_alt | trading_name = trade_name | company_type = type | incorporation = incorporated | dissolved = defunct | location = hq_location | headquarters = hq_location | location_city = hq_location_city | location_country = hq_location_country | locations = num_locations | members = num_members | members_year = num_members_year | profit = net_income | profit_year = net_income_year | subsid = subsidiaries | foundation = founded | homepage = website }}<noinclude> {{documentation}} </noinclude> 2m9aylyfabfoupjov39hsx1mo4u65f4 803473 803472 2026-06-15T22:33:45Z en>Zackmann08 0 cat not needed. This is being dealt with in the deprecation cleanup 803473 wikitext text/x-wiki {{Infobox | bodyclass = ib-company vcard | templatestyles = Infobox company/styles.css | child = {{lc:{{{embed}}}}} | decat = yes | titleclass = fn org | titlestyle = font-size: 125%; | title = {{#ifeq:{{lc:{{{embed}}}}} | yes | '''Company''' | {{if empty|{{{name|}}}|{{{company_name|}}}|{{PAGENAMEBASE}}}} }} | imageclass = ib-company-logo logo | image = {{#invoke:InfoboxImage |InfoboxImage |image={{#ifeq:{{lc:{{{embed}}}}} | yes | {{if empty|{{{logo|}}}|{{{company_logo|}}}}} |{{#invoke:WikidataIB |getValue |rank=best |P154 |name=logo |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=no |noicon=yes |maxvals=1 |{{if empty|{{{logo|}}}|{{{company_logo|}}}}} }} }} |size={{{logo_size|}}} |sizedefault=frameless |upright={{if empty|{{{logo_upright|}}}|1}} |alt={{if empty|{{{logo_alt|}}}|{{{alt|}}}}} |class= {{{logo_class|}}} |maxsize=250px}} | caption = {{{logo_caption|}}} | image2 = {{#invoke:InfoboxImage |InfoboxImage |image={{{image|}}} |size={{{image_size|}}} |upright={{if empty|{{{image_upright|}}}|1}} |alt={{{image_alt|}}} }} | caption2 = {{{image_caption|}}} | label1 = {{longitem|[[Trade name]]}} | data1 = {{if empty|{{{trade_name|}}}|{{{trading_name|}}}}} | label2 = {{longitem|Native name}} | data2 = {{#if:{{{native_name|}}} | {{#if:{{{native_name_lang|}}} | {{lang|{{{native_name_lang}}}|{{{native_name}}} }} | {{{native_name}}} }} }} | label3 = {{longitem|[[Romanization|Romanized]] name}} | data3 = {{{romanized_name|}}} | label4 = Formerly | class4 = nickname | data4 = {{if empty|{{{former_names|}}}|{{{former_name|}}}}} | label5 = {{longitem|Company type}} | class5 = category | data5 = {{if empty|{{{type|}}}|{{{company_type|}}}}} | label6 = {{longitem|[[Ticker symbol|Traded as]]}} | data6 = {{{traded_as|}}} | label7 = [[International Securities Identification Number|ISIN]] | data7 = {{#invoke:WikidataIB |getValue |rank=best |P946 |name=ISIN |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{{ISIN|}}} }} | label8 = Industry | class8 = category | data8 = {{#invoke:WikidataIB |getValue |rank=best |P452 |name=industry |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{{industry|}}} }} | label9 = Genre | class9 = category | data9 = {{{genre|}}} | label10 = Predecessor{{#if:{{{predecessors|}}}|s}} | data10 = {{#invoke:WikidataIB |getValue |rank=best |P155 |name=predecessor |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{predecessors|}}}|{{{predecessor|}}}}} }} | label11 = Incorporated | data11 = {{#invoke:WikidataIB |getValue |rank=best |P1001 |name=incorporated |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{incorporated|}}}|{{{incorporation|}}}}} }} | label12 = Founded | data12 = {{#invoke:WikidataIB |getValue |rank=best |P571 |name=founded |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{founded|}}}|{{{foundation|}}}}} }} | label13 = Founder{{#if:{{{founders|}}}|s}} | class13 = agent | data13 = {{#invoke:WikidataIB |getValue |rank=best |P112 |name=founder |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{founders|}}}|{{{founder|}}}}} }} | label14 = Defunct | data14 = {{#invoke:WikidataIB |getValue |rank=best |P576 |name=defunct |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{defunct|}}}|{{{dissolved|}}}}} }} | label15 = Fate | data15 = {{{fate|}}} | label16 = Successor{{#if:{{{successors|}}}|s}} | data16 = {{#invoke:WikidataIB |getValue |rank=best |P156 |name=successor |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{successors|}}}|{{{successor|}}}}} }} | label17 = Headquarters | class17 = {{#if:{{{hq_location|}}}{{{location|}}}{{{headquarters|}}} | label | adr}} | data17 = {{#if:{{{hq_location|}}}{{{location|}}}{{{headquarters|}}}{{{hq_location_city|}}}{{{location_city|}}}{{{hq_location_country|}}}{{{location_country|}}} | {{Comma separated entries | 1 = {{if empty|{{{hq_location|}}}|{{{location|}}}|{{{headquarters|}}}}} | 2 = {{#if:{{{hq_location_city|}}}{{{location_city|}}} |<div class="ib-company-locality locality">{{#if:{{{hq_location_city|}}}{{{location_city|}}} | {{if empty|{{{hq_location_city|}}}|{{{location_city|}}}}} | }}</div>}} | 3 = {{#if:{{{hq_location_country|}}}{{{location_country|}}} |<div class="ib-company-country country-name">{{#if:{{{hq_location_country|}}}{{{location_country|}}} | {{if empty|{{{hq_location_country|}}}|{{{location_country|}}}}} | }}</div>}} }} | {{#if:{{#invoke:WikidataIB |getValue |rank=best |P159 |name=hq_location_city |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }}{{#invoke:WikidataIB |getValue |rank=best |P17 |name=hq_location_country |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }} | {{Comma separated entries | 1 = {{#if:{{#invoke:WikidataIB |getValue |rank=best |P159 |name=hq_location_city |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }} |<div class="ib-company-locality locality">{{#invoke:WikidataIB |getValue |rank=best |P159 |name=headquarters location |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }}</div>}} | 2 = {{#if:{{#invoke:WikidataIB |getValue |rank=best |P17 |name=countrry |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }} |<div class="ib-company-country country-name">{{#invoke:WikidataIB |getValue |rank=best |P17 |name=hq_location_country |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }}</div>}} }} }} }} | label19 = {{longitem|Number of locations}} | data19 = {{#if:{{{num_locations|}}}{{{locations|}}} | {{if empty|{{{num_locations|}}}|{{{locations|}}}}}{{#if:{{{num_locations_year|}}} | &nbsp;({{{num_locations_year}}}) }} }} | label20 = {{longitem|Area{{#if:{{{areas_served|}}}|s}} served}} | data20 = {{if empty|{{{areas_served|}}}|{{{area_served|}}}}} | label21 = {{longitem|Key people}} | class21 = agent | data21 = {{{key_people|}}} | label22 = Products | data22 = {{{products|}}} | label23 = {{longitem|Production output}} | data23 = {{#if:{{{production|}}} | {{{production|}}}{{#if:{{{production_year|}}} | &nbsp;({{{production_year}}}) }} }} | label24 = Brands | data24 = {{{brands|}}} | label25 = Services | class25 = category | data25 = {{{services|}}} | label26 = Revenue | data26 = {{#invoke:WikidataIB |getValue |rank=best |P2139 |name=revenue |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{revenue|}}}{{#if:{{{revenue_year|}}} | &nbsp;({{{revenue_year}}}) }} }} | label27 = {{longitem|[[Earnings before interest and taxes|Operating income]]}} | data27 = {{#invoke:WikidataIB |getValue |rank=best |P3362 |name=operating_income |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{operating_income|}}}{{#if:{{{income_year|}}} | &nbsp;({{{income_year}}}) }} }} | label28 = {{longitem|[[Net income]]}} | data28 = {{#invoke:WikidataIB |getValue |rank=best |P2295 |name=net_income |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{if empty|{{{net_income|}}}|{{{profit|}}}}}{{#if:{{{net_income_year|}}}{{{profit_year|}}} | &nbsp;({{if empty|{{{net_income_year|}}}|{{{profit_year|}}}}}) }} }} | label29 = [[Assets under management|AUM]] | data29 = {{{aum|}}} | label30 = {{nowrap|[[Asset|Total assets]]}} | data30 = {{#invoke:WikidataIB |getValue |rank=best |P2403 |name=assets |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{assets|}}}{{#if:{{{assets_year|}}} | &nbsp;({{{assets_year}}}) }} }} | label31 = {{nowrap|[[Equity (finance)|Total equity]]}} | data31 = {{#if:{{{equity|}}} | {{{equity}}}{{#if:{{{equity_year|}}} | &nbsp;({{{equity_year}}})}} }} | label32 = Owner{{#if:{{{owners|}}}|s}} | data32 = {{if empty|{{{owners|}}}|{{{owner|}}}}} | label33 = Members | data33 = {{#if:{{{members|}}}{{{num_members|}}} | {{if empty|{{{members|}}}|{{{num_members|}}}}}{{#if:{{{members_year|}}}{{{num_members_year|}}} | &nbsp;({{if empty|{{{members_year|}}}|{{{num_members_year|}}}}})}} }} | label34 = {{longitem|Number of employees}} | data34 = {{#invoke:WikidataIB |getValue |rank=best |P1128 |name=num_employees |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{num_employees|}}}{{#if:{{{num_employees_year|}}} | &nbsp;({{{num_employees_year}}}) }} }} | label35 = [[Parent company|Parent]] | data35 = {{#ifeq:{{{owners|}}}{{{owner|}}}{{{parent|}}} || {{#invoke:WikidataIB |getValue |rank=best |P749 |name=parent |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes }} | {{{parent|}}} }} | label36 = [[Division (business)|Divisions]] | data36 = {{{divisions|}}} | label37 = [[Subsidiary|Subsidiaries]] | data37 = {{if empty|{{{subsid|}}}|{{{subsidiaries|}}}}} | data39 = {{{module|}}} | label40 = [[Basel III|Capital ratio]] | data40 = {{{ratio|}}} | label41 = Rating | data41 = {{{rating|}}} | label42 = Website | data42 = {{#if:{{{website|}}}{{{homepage|}}} |{{if empty|{{{website|}}}|{{{homepage|}}}}} |{{#invoke:WikidataIB |url2 |url={{#invoke:WikidataIB |getValue |rank=best |P856 |name=website |qid={{{qid|}}} |suppressfields={{{suppressfields|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |onlysourced=yes |maxvals=1}} }} }} | below = {{#if:{{{footnotes|}}} | '''Footnotes{{\}}references'''{{break}}{{{footnotes}}} }} }}<!-- Tracking categories: -->{{main other|{{#ifeq:{{if empty|{{{logo|}}}|{{{company_logo|}}}|{{wikidata|property|raw|P154}}}}|{{if empty|{{{logo|}}}|{{{company_logo|}}}}}||[[Category:Pages using infobox company with a logo from wikidata]] }}{{#if:{{{image|}}}|{{#ifeq:{{#invoke:string|replace|{{{image|}}}| |_}}|{{#invoke:string|replace|{{if empty|{{{logo|}}}|{{{company_logo|}}}|{{wikidata|property|raw|P154}}}}| |_}}|[[Category:Pages using infobox company with a duplicate image]]|}}|}} }}<!-- -->{{#invoke:Check for unknown parameters|check|unknown={{main other|[[Category:Pages using infobox company with unknown parameters|_VALUE_{{PAGENAME}}]]}}|preview=Page using [[Template:Infobox company]] with unknown parameter "_VALUE_" | ignoreblank=y | alt | area_served | areas_served | assets | assets_year | aum | brands | company_logo | company_name | company_type | defunct | dissolved | divisions | embed | equity | equity_year | fate | footnotes | headquarters | former_name | former_names | foundation | founded | founder | founders | genre | homepage | hq_location | hq_location_city | hq_location_country | incorporated | image | image_alt | image_caption | image_size | image_upright | income_year | industry | ISIN | key_people | location | location_city | location_country | locations | logo | logo_alt | logo_caption | logo_class | logo_size | logo_upright | members | members_year | module | name | native_name | native_name_lang | net_income | net_income_year | num_employees | num_employees_year | num_locations | num_locations_year | operating_income | owner | owners | parent | predecessor | predecessors | production | production_year | products | profit | profit_year | rating | ratio | revenue | revenue_year | romanized_name | services | subsid | subsidiaries | successor | successors | traded_as | trade_name | trading_name | type | website| qid | fetchwikidata | suppressfields | noicon | nocat | demo | categories | num_members | num_members_year }}{{#invoke:Check for conflicting parameters|check|nested=1 | template = [[Template:Infobox company]] | cat = {{main other|Category:Pages using infobox company with conflicting parameters}} | logo; company_logo | logo_alt; alt | trade_name; trading_name | former_names; former_name | type; company_type | predecessors; predecessor | incorporated; incorporation | founded; foundation | founders; founder | defunct; dissolved | successors; successor | hq_location; location; headquarters | hq_location_city; location_city | hq_location_country; location_country | num_locations; locations | areas_served; area_served | net_income; profit | net_income_year; profit_year | owners; owner | subsid; subsidiaries | website; homepage }}{{#invoke:Check for deprecated parameters|check | _category = {{main other|[[Category:Pages using infobox company with deprecated parameters|_VALUE_]]}} | company_name = name | company_logo = logo | alt = logo_alt | trading_name = trade_name | company_type = type | incorporation = incorporated | dissolved = defunct | location = hq_location | headquarters = hq_location | location_city = hq_location_city | location_country = hq_location_country | locations = num_locations | members = num_members | members_year = num_members_year | profit = net_income | profit_year = net_income_year | subsid = subsidiaries | foundation = founded | homepage = website }}<noinclude> {{documentation}} </noinclude> 2epeqyg1ciypk7z3e1fpjm5z0tzce9b 803474 803473 2026-06-16T21:34:52Z en>Zackmann08 0 removing support for deprecated parameters that have been cleaned. 803474 wikitext text/x-wiki {{Infobox | bodyclass = ib-company vcard | templatestyles = Infobox company/styles.css | child = {{lc:{{{embed}}}}} | decat = yes | titleclass = fn org | titlestyle = font-size: 125%; | title = {{#ifeq:{{lc:{{{embed}}}}} | yes | '''Company''' | {{if empty|{{{name|}}}|{{PAGENAMEBASE}}}} }} | imageclass = ib-company-logo logo | image = {{#invoke:InfoboxImage |InfoboxImage |image={{#ifeq:{{lc:{{{embed}}}}} | yes | {{{logo|}}} |{{#invoke:WikidataIB |getValue |rank=best |P154 |name=logo |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=no |noicon=yes |maxvals=1 |{{{logo|}}} }} }} |size={{{logo_size|}}} |sizedefault=frameless |upright={{if empty|{{{logo_upright|}}}|1}} |alt={{{logo_alt|}}} |class= {{{logo_class|}}} |maxsize=250px}} | caption = {{{logo_caption|}}} | image2 = {{#invoke:InfoboxImage |InfoboxImage |image={{{image|}}} |size={{{image_size|}}} |upright={{if empty|{{{image_upright|}}}|1}} |alt={{{image_alt|}}} }} | caption2 = {{{image_caption|}}} | label1 = {{longitem|[[Trade name]]}} | data1 = {{{trade_name|}}} | label2 = {{longitem|Native name}} | data2 = {{#if:{{{native_name|}}} | {{#if:{{{native_name_lang|}}} | {{lang|{{{native_name_lang}}}|{{{native_name}}} }} | {{{native_name}}} }} }} | label3 = {{longitem|[[Romanization|Romanized]] name}} | data3 = {{{romanized_name|}}} | label4 = Formerly | class4 = nickname | data4 = {{if empty|{{{former_names|}}}|{{{former_name|}}}}} | label5 = Type | class5 = category | data5 = {{{type|}}} | label6 = {{longitem|[[Ticker symbol|Traded as]]}} | data6 = {{{traded_as|}}} | label7 = [[International Securities Identification Number|ISIN]] | data7 = {{#invoke:WikidataIB |getValue |rank=best |P946 |name=ISIN |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{{ISIN|}}} }} | label8 = Industry | class8 = category | data8 = {{#invoke:WikidataIB |getValue |rank=best |P452 |name=industry |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{{industry|}}} }} | label9 = Genre | class9 = category | data9 = {{{genre|}}} | label10 = Predecessor{{#if:{{{predecessors|}}}|s}} | data10 = {{#invoke:WikidataIB |getValue |rank=best |P155 |name=predecessor |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{predecessors|}}}|{{{predecessor|}}}}} }} | label11 = Incorporated | data11 = {{#invoke:WikidataIB |getValue |rank=best |P1001 |name=incorporated |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{{incorporated|}}} }} | label12 = Founded | data12 = {{#invoke:WikidataIB |getValue |rank=best |P571 |name=founded |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes | {{{founded|}}} }} | label13 = Founder{{#if:{{{founders|}}}|s}} | class13 = agent | data13 = {{#invoke:WikidataIB |getValue |rank=best |P112 |name=founder |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{founders|}}}|{{{founder|}}}}} }} | label14 = Defunct | data14 = {{#invoke:WikidataIB |getValue |rank=best |P576 |name=defunct |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{{defunct|}}} }} | label15 = Fate | data15 = {{{fate|}}} | label16 = Successor{{#if:{{{successors|}}}|s}} | data16 = {{#invoke:WikidataIB |getValue |rank=best |P156 |name=successor |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{successors|}}}|{{{successor|}}}}} }} | label17 = Headquarters | class17 = {{#if:{{{hq_location|}}} | label | adr}} | data17 = {{#if:{{{hq_location|}}}{{{hq_location_city|}}}{{{hq_location_country|}}} | {{Comma separated entries | 1 = {{{hq_location|}}} | 2 = {{#if:{{{hq_location_city|}}} |<div class="ib-company-locality locality"> {{{hq_location_city|}}}</div>}} | 3 = {{#if:{{{hq_location_country|}}}} |<div class="ib-company-country country-name"> {{{hq_location_country|}}}</div>}} }} | {{#if:{{#invoke:WikidataIB |getValue |rank=best |P159 |name=hq_location_city |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }}{{#invoke:WikidataIB |getValue |rank=best |P17 |name=hq_location_country |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }} | {{Comma separated entries | 1 = {{#if:{{#invoke:WikidataIB |getValue |rank=best |P159 |name=hq_location_city |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }} |<div class="ib-company-locality locality">{{#invoke:WikidataIB |getValue |rank=best |P159 |name=headquarters location |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }}</div>}} | 2 = {{#if:{{#invoke:WikidataIB |getValue |rank=best |P17 |name=countrry |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }} |<div class="ib-company-country country-name">{{#invoke:WikidataIB |getValue |rank=best |P17 |name=hq_location_country |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }}</div>}} }} }} }} | label19 = {{longitem|Number of locations}} | data19 = {{{num_locations|}}}{{#if:{{{num_locations_year|}}} | &nbsp;({{{num_locations_year}}}) }} | label20 = {{longitem|Area{{#if:{{{areas_served|}}}|s}} served}} | data20 = {{if empty|{{{areas_served|}}}|{{{area_served|}}}}} | label21 = {{longitem|Key people}} | class21 = agent | data21 = {{{key_people|}}} | label22 = Products | data22 = {{{products|}}} | label23 = {{longitem|Production output}} | data23 = {{#if:{{{production|}}} | {{{production|}}}{{#if:{{{production_year|}}} | &nbsp;({{{production_year}}}) }} }} | label24 = Brands | data24 = {{{brands|}}} | label25 = Services | class25 = category | data25 = {{{services|}}} | label26 = Revenue | data26 = {{#invoke:WikidataIB |getValue |rank=best |P2139 |name=revenue |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{revenue|}}}{{#if:{{{revenue_year|}}} | &nbsp;({{{revenue_year}}}) }} }} | label27 = {{longitem|[[Earnings before interest and taxes|Operating income]]}} | data27 = {{#invoke:WikidataIB |getValue |rank=best |P3362 |name=operating_income |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{operating_income|}}}{{#if:{{{income_year|}}} | &nbsp;({{{income_year}}}) }} }} | label28 = {{longitem|[[Net income]]}} | data28 = {{#invoke:WikidataIB |getValue |rank=best |P2295 |name=net_income |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{net_income|}}}{{#if:{{{net_income_year|}}} | &nbsp;({{{net_income_year|}}}) }} }} | label29 = [[Assets under management|AUM]] | data29 = {{{aum|}}} | label30 = {{nowrap|[[Asset|Total assets]]}} | data30 = {{#invoke:WikidataIB |getValue |rank=best |P2403 |name=assets |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{assets|}}}{{#if:{{{assets_year|}}} | &nbsp;({{{assets_year}}}) }} }} | label31 = {{nowrap|[[Equity (finance)|Total equity]]}} | data31 = {{#if:{{{equity|}}} | {{{equity}}}{{#if:{{{equity_year|}}} | &nbsp;({{{equity_year}}})}} }} | label32 = Owner{{#if:{{{owners|}}}|s}} | data32 = {{if empty|{{{owners|}}}|{{{owner|}}}}} | label33 = Members | data33 = {{{num_members|}}}{{#if:{{{num_members_year|}}} | &nbsp;({{{num_members_year|}}})}} | label34 = {{longitem|Number of employees}} | data34 = {{#invoke:WikidataIB |getValue |rank=best |P1128 |name=num_employees |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{num_employees|}}}{{#if:{{{num_employees_year|}}} | &nbsp;({{{num_employees_year}}}) }} }} | label35 = [[Parent company|Parent]] | data35 = {{#ifeq:{{{owners|}}}{{{owner|}}}{{{parent|}}} || {{#invoke:WikidataIB |getValue |rank=best |P749 |name=parent |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes }} | {{{parent|}}} }} | label36 = [[Division (business)|Divisions]] | data36 = {{{divisions|}}} | label37 = [[Subsidiary|Subsidiaries]] | data37 = {{{subsidiaries|}}} | data39 = {{{module|}}} | label40 = [[Basel III|Capital ratio]] | data40 = {{{ratio|}}} | label41 = Rating | data41 = {{{rating|}}} | label42 = Website | data42 = {{#if:{{{website|}}}| {{{website|}}} |{{#invoke:WikidataIB |url2 |url={{#invoke:WikidataIB |getValue |rank=best |P856 |name=website |qid={{{qid|}}} |suppressfields={{{suppressfields|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |onlysourced=yes |maxvals=1}} }} }} | below = {{#if:{{{footnotes|}}} | '''Footnotes'''{{break}}{{{footnotes}}} }} }}<!-- Tracking categories: -->{{main other|{{#ifeq:{{if empty|{{{logo|}}}|{{wikidata|property|raw|P154}}}}|{{{logo|}}}||[[Category:Pages using infobox company with a logo from wikidata]] }}{{#if:{{{image|}}}|{{#ifeq:{{#invoke:string|replace|{{{image|}}}| |_}}|{{#invoke:string|replace|{{if empty|{{{logo|}}}|{{wikidata|property|raw|P154}}}}| |_}}|[[Category:Pages using infobox company with a duplicate image]]|}}|}} }}<!-- -->{{#invoke:Check for unknown parameters|check|unknown={{main other|[[Category:Pages using infobox company with unknown parameters|_VALUE_{{PAGENAME}}]]}}|preview=Page using [[Template:Infobox company]] with unknown parameter "_VALUE_"|ignoreblank=y| area_served | areas_served | assets | assets_year | aum | brands | defunct | divisions | embed | equity | equity_year | fate | fetchwikidata | footnotes | former_name | former_names | founded | founder | founders | genre | hq_location | hq_location_city | hq_location_country | image | image_alt | image_caption | image_size | image_upright | income_year | incorporated | industry | ISIN | key_people | logo | logo_alt | logo_caption | logo_class | logo_size | logo_upright | module | name | native_name | native_name_lang | net_income | net_income_year | noicon | num_employees | num_employees_year | num_locations | num_locations_year | num_members | num_members_year | operating_income | owner | owners | parent | predecessor | predecessors | production | production_year | products | qid | rating | ratio | revenue | revenue_year | romanized_name | services | subsidiaries | successor | successors | suppressfields | trade_name | traded_as | type | website }}{{#invoke:Check for conflicting parameters|check | template = [[Template:Infobox company]] | cat = {{main other|Category:Pages using infobox company with conflicting parameters}} | former_names; former_name | predecessors; predecessor | founders; founder | successors; successor | areas_served; area_served | owners; owner }}<noinclude> {{documentation}} </noinclude> 1qg74qt2zwapm7b4zvv5wux8vr6ww6l 803475 803474 2026-06-25T11:34:46Z en>Primefac 0 rmv excess } 803475 wikitext text/x-wiki {{Infobox | bodyclass = ib-company vcard | templatestyles = Infobox company/styles.css | child = {{lc:{{{embed}}}}} | decat = yes | titleclass = fn org | titlestyle = font-size: 125%; | title = {{#ifeq:{{lc:{{{embed}}}}} | yes | '''Company''' | {{if empty|{{{name|}}}|{{PAGENAMEBASE}}}} }} | imageclass = ib-company-logo logo | image = {{#invoke:InfoboxImage |InfoboxImage |image={{#ifeq:{{lc:{{{embed}}}}} | yes | {{{logo|}}} |{{#invoke:WikidataIB |getValue |rank=best |P154 |name=logo |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=no |noicon=yes |maxvals=1 |{{{logo|}}} }} }} |size={{{logo_size|}}} |sizedefault=frameless |upright={{if empty|{{{logo_upright|}}}|1}} |alt={{{logo_alt|}}} |class= {{{logo_class|}}} |maxsize=250px}} | caption = {{{logo_caption|}}} | image2 = {{#invoke:InfoboxImage |InfoboxImage |image={{{image|}}} |size={{{image_size|}}} |upright={{if empty|{{{image_upright|}}}|1}} |alt={{{image_alt|}}} }} | caption2 = {{{image_caption|}}} | label1 = {{longitem|[[Trade name]]}} | data1 = {{{trade_name|}}} | label2 = {{longitem|Native name}} | data2 = {{#if:{{{native_name|}}} | {{#if:{{{native_name_lang|}}} | {{lang|{{{native_name_lang}}}|{{{native_name}}} }} | {{{native_name}}} }} }} | label3 = {{longitem|[[Romanization|Romanized]] name}} | data3 = {{{romanized_name|}}} | label4 = Formerly | class4 = nickname | data4 = {{if empty|{{{former_names|}}}|{{{former_name|}}}}} | label5 = Type | class5 = category | data5 = {{{type|}}} | label6 = {{longitem|[[Ticker symbol|Traded as]]}} | data6 = {{{traded_as|}}} | label7 = [[International Securities Identification Number|ISIN]] | data7 = {{#invoke:WikidataIB |getValue |rank=best |P946 |name=ISIN |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{{ISIN|}}} }} | label8 = Industry | class8 = category | data8 = {{#invoke:WikidataIB |getValue |rank=best |P452 |name=industry |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{{industry|}}} }} | label9 = Genre | class9 = category | data9 = {{{genre|}}} | label10 = Predecessor{{#if:{{{predecessors|}}}|s}} | data10 = {{#invoke:WikidataIB |getValue |rank=best |P155 |name=predecessor |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{predecessors|}}}|{{{predecessor|}}}}} }} | label11 = Incorporated | data11 = {{#invoke:WikidataIB |getValue |rank=best |P1001 |name=incorporated |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{{incorporated|}}} }} | label12 = Founded | data12 = {{#invoke:WikidataIB |getValue |rank=best |P571 |name=founded |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes | {{{founded|}}} }} | label13 = Founder{{#if:{{{founders|}}}|s}} | class13 = agent | data13 = {{#invoke:WikidataIB |getValue |rank=best |P112 |name=founder |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{founders|}}}|{{{founder|}}}}} }} | label14 = Defunct | data14 = {{#invoke:WikidataIB |getValue |rank=best |P576 |name=defunct |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{{defunct|}}} }} | label15 = Fate | data15 = {{{fate|}}} | label16 = Successor{{#if:{{{successors|}}}|s}} | data16 = {{#invoke:WikidataIB |getValue |rank=best |P156 |name=successor |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes |{{if empty|{{{successors|}}}|{{{successor|}}}}} }} | label17 = Headquarters | class17 = {{#if:{{{hq_location|}}} | label | adr}} | data17 = {{#if:{{{hq_location|}}}{{{hq_location_city|}}}{{{hq_location_country|}}} | {{Comma separated entries | 1 = {{{hq_location|}}} | 2 = {{#if:{{{hq_location_city|}}} |<div class="ib-company-locality locality"> {{{hq_location_city|}}}</div>}} | 3 = {{#if:{{{hq_location_country|}}} |<div class="ib-company-country country-name"> {{{hq_location_country|}}}</div>}} }} | {{#if:{{#invoke:WikidataIB |getValue |rank=best |P159 |name=hq_location_city |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }}{{#invoke:WikidataIB |getValue |rank=best |P17 |name=hq_location_country |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }} | {{Comma separated entries | 1 = {{#if:{{#invoke:WikidataIB |getValue |rank=best |P159 |name=hq_location_city |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }} |<div class="ib-company-locality locality">{{#invoke:WikidataIB |getValue |rank=best |P159 |name=headquarters location |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }}</div>}} | 2 = {{#if:{{#invoke:WikidataIB |getValue |rank=best |P17 |name=countrry |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }} |<div class="ib-company-country country-name">{{#invoke:WikidataIB |getValue |rank=best |P17 |name=hq_location_country |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 }}</div>}} }} }} }} | label19 = {{longitem|Number of locations}} | data19 = {{{num_locations|}}}{{#if:{{{num_locations_year|}}} | &nbsp;({{{num_locations_year}}}) }} | label20 = {{longitem|Area{{#if:{{{areas_served|}}}|s}} served}} | data20 = {{if empty|{{{areas_served|}}}|{{{area_served|}}}}} | label21 = {{longitem|Key people}} | class21 = agent | data21 = {{{key_people|}}} | label22 = Products | data22 = {{{products|}}} | label23 = {{longitem|Production output}} | data23 = {{#if:{{{production|}}} | {{{production|}}}{{#if:{{{production_year|}}} | &nbsp;({{{production_year}}}) }} }} | label24 = Brands | data24 = {{{brands|}}} | label25 = Services | class25 = category | data25 = {{{services|}}} | label26 = Revenue | data26 = {{#invoke:WikidataIB |getValue |rank=best |P2139 |name=revenue |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{revenue|}}}{{#if:{{{revenue_year|}}} | &nbsp;({{{revenue_year}}}) }} }} | label27 = {{longitem|[[Earnings before interest and taxes|Operating income]]}} | data27 = {{#invoke:WikidataIB |getValue |rank=best |P3362 |name=operating_income |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{operating_income|}}}{{#if:{{{income_year|}}} | &nbsp;({{{income_year}}}) }} }} | label28 = {{longitem|[[Net income]]}} | data28 = {{#invoke:WikidataIB |getValue |rank=best |P2295 |name=net_income |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{net_income|}}}{{#if:{{{net_income_year|}}} | &nbsp;({{{net_income_year|}}}) }} }} | label29 = [[Assets under management|AUM]] | data29 = {{{aum|}}} | label30 = {{nowrap|[[Asset|Total assets]]}} | data30 = {{#invoke:WikidataIB |getValue |rank=best |P2403 |name=assets |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{assets|}}}{{#if:{{{assets_year|}}} | &nbsp;({{{assets_year}}}) }} }} | label31 = {{nowrap|[[Equity (finance)|Total equity]]}} | data31 = {{#if:{{{equity|}}} | {{{equity}}}{{#if:{{{equity_year|}}} | &nbsp;({{{equity_year}}})}} }} | label32 = Owner{{#if:{{{owners|}}}|s}} | data32 = {{if empty|{{{owners|}}}|{{{owner|}}}}} | label33 = Members | data33 = {{{num_members|}}}{{#if:{{{num_members_year|}}} | &nbsp;({{{num_members_year|}}})}} | label34 = {{longitem|Number of employees}} | data34 = {{#invoke:WikidataIB |getValue |rank=best |P1128 |name=num_employees |qual=P585 |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |maxvals=1 |{{{num_employees|}}}{{#if:{{{num_employees_year|}}} | &nbsp;({{{num_employees_year}}}) }} }} | label35 = [[Parent company|Parent]] | data35 = {{#ifeq:{{{owners|}}}{{{owner|}}}{{{parent|}}} || {{#invoke:WikidataIB |getValue |rank=best |P749 |name=parent |qid={{{qid|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |suppressfields={{{suppressfields|}}} |onlysourced=yes |noicon={{if empty|{{{noicon|}}}|no}} |sep="<br />" |sorted=yes }} | {{{parent|}}} }} | label36 = [[Division (business)|Divisions]] | data36 = {{{divisions|}}} | label37 = [[Subsidiary|Subsidiaries]] | data37 = {{{subsidiaries|}}} | data39 = {{{module|}}} | label40 = [[Basel III|Capital ratio]] | data40 = {{{ratio|}}} | label41 = Rating | data41 = {{{rating|}}} | label42 = Website | data42 = {{#if:{{{website|}}}| {{{website|}}} |{{#invoke:WikidataIB |url2 |url={{#invoke:WikidataIB |getValue |rank=best |P856 |name=website |qid={{{qid|}}} |suppressfields={{{suppressfields|}}} |fetchwikidata={{if empty|{{{fetchwikidata|}}}|ALL}} |onlysourced=yes |maxvals=1}} }} }} | below = {{#if:{{{footnotes|}}} | '''Footnotes'''{{break}}{{{footnotes}}} }} }}<!-- Tracking categories: -->{{main other|{{#ifeq:{{if empty|{{{logo|}}}|{{wikidata|property|raw|P154}}}}|{{{logo|}}}||[[Category:Pages using infobox company with a logo from wikidata]] }}{{#if:{{{image|}}}|{{#ifeq:{{#invoke:string|replace|{{{image|}}}| |_}}|{{#invoke:string|replace|{{if empty|{{{logo|}}}|{{wikidata|property|raw|P154}}}}| |_}}|[[Category:Pages using infobox company with a duplicate image]]|}}|}} }}<!-- -->{{#invoke:Check for unknown parameters|check|unknown={{main other|[[Category:Pages using infobox company with unknown parameters|_VALUE_{{PAGENAME}}]]}}|preview=Page using [[Template:Infobox company]] with unknown parameter "_VALUE_"|ignoreblank=y| area_served | areas_served | assets | assets_year | aum | brands | defunct | divisions | embed | equity | equity_year | fate | fetchwikidata | footnotes | former_name | former_names | founded | founder | founders | genre | hq_location | hq_location_city | hq_location_country | image | image_alt | image_caption | image_size | image_upright | income_year | incorporated | industry | ISIN | key_people | logo | logo_alt | logo_caption | logo_class | logo_size | logo_upright | module | name | native_name | native_name_lang | net_income | net_income_year | noicon | num_employees | num_employees_year | num_locations | num_locations_year | num_members | num_members_year | operating_income | owner | owners | parent | predecessor | predecessors | production | production_year | products | qid | rating | ratio | revenue | revenue_year | romanized_name | services | subsidiaries | successor | successors | suppressfields | trade_name | traded_as | type | website }}{{#invoke:Check for conflicting parameters|check | template = [[Template:Infobox company]] | cat = {{main other|Category:Pages using infobox company with conflicting parameters}} | former_names; former_name | predecessors; predecessor | founders; founder | successors; successor | areas_served; area_served | owners; owner }}<noinclude> {{documentation}} </noinclude> h0ejwsqmtky2x3w9jjx4hh62mt5l9bp अशोक 0 28560 803313 765801 2026-07-29T16:20:12Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803313 wikitext text/x-wiki {{Infobox royalty |title = चक्रवर्ती <ref name="Fogelin2015">{{cite book|author=Lars Fogelin|title=An Archaeological History of Indian Buddhism|url=https://books.google.com/books?id=yPZzBgAAQBAJ&pg=PA81|date=1 अप्रैल 2015|publisher=Oxford University Press|isbn=978-0-19-994823-9|pages=81–}}</ref><ref name="Kleiner2015">{{cite book|author=Fred Kleiner|title=Gardner’s Art through the Ages: A Global History|url=https://books.google.com/books?id=q4bCBAAAQBAJ&pg=PT474|date=1 जनवरी 2015|publisher=Cengage Learning|isbn=978-1-305-54484-0|pages=474–}}</ref> |image =Indian_relief_from_Amaravati,_Guntur._Preserved_in_Guimet_Museum.jpg |caption = [[अमरावती गाँव, गुंटूर जिला|अमरावती]], [[आंध्रप्रदेश]] (भारत) में लगभग 1ली सदी ईपू/ईसवी के एगो चित्र, बीच में शाईट अशोक के देखावल गइल बाटे। |succession= तीसरका मौर्य सम्राट |reign = 268 ईपू — ल॰ 232 ईपू{{sfn|Upinder Singh|2008|p=331}} |coronation = 268 ईपू{{sfn|Upinder Singh|2008|p=331}} |predecessor = [[बिंदुसार]] |successor = [[दशरथ मौर्य|दशरथ]] |othertitles= ''[[देवानंपिय]]'', ''[[पियदसी]]'' |spouse=[[असंधिमित्र]] |spouse-type=साथी |spouses={{unbulleted list|[[देवी (असोक के मेहरारू)|देवी]] |[[करुवाकी]] |[[रानी पद्मावती|पद्मावती]] |[[तिष्यारक्षा]]}} |issue={{unbulleted list|[[महिंद]] |[[संघमित्ता]] |[[तिवाला]] |[[कुनाल]] |[[चारुमती]]}} |spouses-type =पत्नी |dynasty = [[मौर्य साम्राज्य|मौर्य]] |father = [[बिंदुसार]] |mother = [[सुभद्रांगी]] |birth_date = 304 ईपू |birth_place = [[पाटलिपुत्र]], [[पटना]] |religion = <!-- Do not add anything here. (See talk.) The reader can read about Ashoka's religion in the main article. !--> |death_date = 232 ईपू (उमिर 72) |death_place = [[पाटलिपुत्र]], [[पटना]] }} {{Maurya}} '''अशोक''' (304—232 ईपू),<ref>{{cite web|last=Chandra |first=Amulya |url=http://www.britannica.com/biography/Ashoka |title=Ashoka &#124; biography - emperor of India |publisher=Britannica.com |date=2015-05-14 |accessdate=2015-08-09}}</ref> चाहे '''असोक महान''' [[मौर्य वंश]] के एगो भारतीय राजा रहलें जे {{circa|268}} से 232 ईपू ले लगभग मय भारत पऽ राज कइलें।<ref>Thapur (1973), p. 51.</ref> भारत के महान राजा लोग मे से एगो, असोक के राज उत्तर-पच्छिम मे हिंदुकुश से ले के पूरुब मे आज के बांग्लादेश ले, आ दक्खिन के ओर आजकाल के तमिलनाडु आ केरल के कुछ हिस्सा छोड़ के बाकी मय भारत के छेंकले रहे। एह राज के राजधानी [[मगध]] मे [[पाटलिपुत्र]] (वर्तमान [[पटना]]) रहल आ [[उज्जैन]] आ तक्षशिला में क्षेत्रीय राजधानी रहलीं सऽ। [[चित्र:Maurya empire in 265 BCE.jpg|thumb|left|320px|सम्राट अशोक के समय में [[मौर्य साम्राज्य]]]] अशोक के जीवन के सभसे बड़ घटना [[कलिंग के लड़ाई]] रहल जेकरे बाद ऊ लड़ाई आ हत्या के तेज के [[बौद्ध धरम]] के मान लिहलें आ सांति के पसार मे लाग गइलें। अशोक के घड़ी मे [[तीसरी बौद्ध संगीति|तीसरका बौद्ध संगीति]] पाटलिपुत्र मे भइल आ ऊ भारत से बाहर के देस सभ में बुद्ध के उपदेस पसारे ला धम्म के पसार करे ला लोग भेजलें। अशोक कय जगह प खम्भा गड़ववलें जिनहन प नैतिक उपदेश खोदवावल रहल। इन्हन के आज [[अशोक स्तंभ]] कहल जाला। भारत के राष्ट्रीय चीन्हा [[सारनाथ]] के अशोक स्तंभ से लिहल गइल बाटे। == नाँव == [[अशोक के आदेशलेख|आदेशलेख]] सभ में '''अशोक''' नाँव दू बेर आइल बा, पहिला मास्की के लेख में जेने सुरूआते ''देवनांपियस अशोकस'' से भइल बा, आ दुसरा बेर गुर्जरा के अभिलेख में अशोक नाँव आइल बा। अबले के अनुमान से इहे साबित भइल बा की बाकी आदेशलेखन में जेकरा के '''पियदसि''' आ ''देवानांपिय'' कहल गइल बा ऊ उहे राजा हवें जिनके बौद्ध ग्रंथन में '''अशोक''' बतावल गइल बा आ पुराणन में '''अशोकवर्द्धन''' कहल गइल बा।<ref name="Shastri2007" /> हालाँकि, नीलकंठ शास्त्री के कहनाम बा कि पियदसि इनकर असली नाँव रहल आ अशोक {{abbr|विरुद|आदर में दिहल नाँव}}, या फिर अशोक असली नाँव रहल आ पियदसि विरुद रहल, ई बात तयजिवन जिइल कठिन बा<ref name="Shastri2007">{{cite book|author=के ए नीलकंठ शास्त्री|title=नंद-मौर्य युगीन भारत|url=https://books.google.com/books?id=T85aSmU7BGoC&pg=PA233|year=2007|publisher=मोतीलाल बनारसीदास|isbn=978-81-208-2104-0|pages=233–}}</ref> == जीवन == '''अशोक के शुरुआती जीवन''' चक्रवर्ती अशोक सम्राट [[बिंदुसार]] आउर रानी धम्मा के बेटा रहन।<ref>https://en.m.wikipedia.org/wiki/Ashoka#CITEREFStrong1989</ref> लंका के परम्परा में बिंदुसार के सोरह पटरानियन आउर 101 बेटन के उल्लेख बा। बेटन में खाली तीन गो के नाँव लिखल बा, [[सूसिम]] जवन सभसे बड़ रहन, अशोक आऊर तिष्य। तिष्य अशोक के सहोदरदभाई आऊर सभसे छोट रहन ।एक दिन ध्म्मा के सपना आइल जे उनकर बेटा बहूत बड़ सम्राट बनी। ओकरा बादे से राजा बिन्दुसार ओकरा आपन रानी बना लिहलन बाकिर धर्मा क्षत्रिय कुल के ना रहे, तऽ ओकरा कवनो बिशेस स्थान ना मिलल रहे। अशोक के कय गो (सौतेला) भाई — बहिन रहऽसन आउर ओकनी मे कड़ा प्रतिस्पर्धा रहत रहे । अशोक के घड़ी के ऊकेरल गइल प्रतीतात्मक चिह्न जेकरा हमनी के '[[अशोक चिह्न]]' के नाँव से जानेनी जा आज उ भारत के राष्ट्रीय चिह्न हऽ। [[बौद्ध धर्म]] के इतिहास में [[गौतम बुद्ध]] के बाद अशोके के नाँव आवेला। दिव्यदान में अशोक के एगो मेहरारु के नाँव 'तिष्यरक्षिता' मिलेला। ओकर लेखा में खाली ओकर मेहरारु 'करूणावकि' बिया। दिव्यादान में अशोक के दु भाइयन सुसीम आ विगताशोक के नाम लिखल बड़ुवे। अशोक मौर्य सम्राट [[चंद्रगुप्त मौर्य]] के पोता रहन। रोमन इतिहासकार अपियन के मोताबिक, चन्द्रगुप्त [[सैल्युकस]] के बेटी से बियाह कइले रहे; एही चलते इहो ह़ो सकेला कि अशोक के आजी युनानी होइहेँ।<ref>https://books.google.fr/books?id=sMoLhNQ9KRoC&pg=PA393#v=onepage&q&f=false</ref><ref>[https://books.google.fr/books?id=Kwnv3I6qIosC&pg=PA86 द] ब्रीफ हिस्टरी ऑफ इंडिया </ref> भारतीय ग्रंथ [[भविष्य पुराण]] में भी एह बात के लिखल गइल बा कि चंद्रगुप्त के बियाह सैल्युकस के बेटी से भइल रहे।<ref>फौरन इन्फ्लुएंस ऑन इंडिया, कृष्णा चन्द्र सागर,[https://books.google.co.in/books?id=0UA4rkm9MgkC&pg=PA83&redir_esc=y#v=onepage&q&f=false 1992]</ref> पुरान बुद्ध, जैन आ हिन्दु लेखन‌ से अशोक के बारे में अलग-अलग बात पता चलेला। [[अवदान साहित्य]] मे लिखल बा कि अशोक रानी सुभद्रंगी के बेटा रहन। अशोकवदन में लिखल बा कि अशोक के माई चम्पा नगर के एगो ब्राह्मण के बेटी रही।<ref>http://www.tandfonline.com/doi/abs/10.1080/02666030.2012.725581</ref><ref>रेलिक्स ऑफ द बुद्ध, स्ट्रॉन्ग [https://books.google.co.in/books?id=xqAB6PKyP98C&pg=PA136&redir_esc=y#v=onepage&q&f=false जॉन]</ref> अशोक के नाँव उनके धइल रहे। दिव्यदानो मे अइसने कहानी लिखल बा लेकिन माई के नाँव जनकपदकल्यानी लिखल बा। '''सत्ता के बिस्तार''' 272इशा पूर्व में राजा बिन्दुसार के मरला के बाद राजगद्दी खातिर लड़ाई चालू हो गइल।दिव्यदान में लिखल बा कि, बिन्दुसार आपन बड़ बेटा [[सूसिम]] के आपन उत्तराधिकारी बनावल चाहत रहन, बाकिर अशोक के आपन पिता के मंत्री लोगन के सहजोग मिलल रहे। मंत्री लोग सूसिम के अहंकारी मानत रहन, आउर ई लोग के लागत रहे की सूसिम उ लोग के इज्जत ना करी।<ref>[a][https://books.google.co.in/books?id=jbjT9GG__nEC&pg=PA268&redir_esc=y#v=onepage&q&f=false]</ref> पता लागेला कि एगो रधागुप्त नांव के मंत्री अशोक के राजगद्दी ले पहुचावे में महत्वपूर्ण भूमिका निभवले रहे। अशोकवदन मे एह बात के लीखल गइल बा कि राजा बनला के बाद अशोक रधागुप्त के प्रधानमंत्री बनवले रहन। [[दिपवंश]] आ [[महावंश]] मे लिखल बा कि अशोक आपन 99 भाईयन के मार के राजा बनल रहन, बाकिर एह बात के सबूत ना मिले। राजा बनला के बाद अशोक आपन राज्य के बहुत तेजी से बिस्तार कीइलें। हुनकर राज उत्तर-पच्छिम में हिंदुकुश से ले के पूरुब में वर्तमान बांग्लादेश ले, आ दक्खिन के ओरे आजकाल के तमिलनाडु आ केरल के कुछ हिस्सा छोड़ के बाकी मय भारत प बिस्तार वाला रहल, उनकर राज बलुचिस्तान आ अफगानिस्तानो पे रहल। '''बियाह''' [[चित्र:Emperor Ashoka and his Queen at the Deer Park.jpg|thumb|middle|अशोक आपन रानी लोग साथे]] <small>अलग-अलग स्त्रोतन से पता लागेला कि अशोक के पाँच गो बियाह भइल रहे। उ लोग के नाँव देवी, करुवाकी, तिष्यारक्षा पद्मावती आउर असन्धिमित्रा रहे। अइसहीँ ईहो पता लागेला कि अशोक चार बेटा आ दुगो बेटी के बाप रहन। बेटन के नाम महेन्द्र(देवी के बेटा), तिवारा (करुवाकी के बेटा), कुनाल (पद्मावती के बेटा), आउर जकुला रहे। बेटी लोग के नाम संघमित्रा(देवी के बेटी) आ चारूमती रहे।</small><ref>Ashoka by मुखर्जी [https://books.google.co.in/books?id=uXyftdtE1ygC रधकुमुंद]</ref> '''कलिंगा पऽ बिजय आ बुद्ध धर्म में परिवर्तन''' भले अशोक के जीवन के जीवन खून खराबा से भरल रहे बाकिर कलिंगा पऽ बिजय हासिल कइला के बाद उ बुद्ध धर्म के अपना लिहलें।[[कलिंग]] राज्य ओह समय में एगो गणतांत्रिक राज्य रहे जवन की ओह घड़ी के भारत में एगो अपवाद नियन रहे, काहे की ओह घड़ी राजतंत्र रहे। अशोक के गद्दी पऽ बइठला के आठ साल बाद [[कलिंग युद्ध]] भइल। शिलालेखन से पता लागेला की युद्ध बड़ी भयानक रहे जेमे लगभग एक लाख सैनिक मारल गइलें। कहल जाला की कलिंग पऽ बिजय हासिल कइला के बाद , एक दिन अशोक नगर घूमे गइलें जेने उनका खाली तबाही लउकल। युद्ध से भइल ओह तबाही उनका के बदल दिहलस, आ ओकर बाद उ बुद्ध धर्म के अपना लिहलन। '''देहांत आउर बिरासत''' अशोक 36 साल लें राज कइलन, हुनकर 232 ई•पु में देहांत हो गइल। कहल जाला की हुनकर अंतिम संस्कार बजी उनकर लाश सात दिन आउर सात रातन ले जलत रहल।उनका मरला के बाद [[मौर्य साम्राज्य]] 50 साल में ही खतम हो गइल। 185 ई•पु मे अंतिम मौर्य सम्राट बृहदरथ के हत्या हुनकर सेनापति द्वारा कर दिहल गइल। जेकरा बाद [[पुष्पमित्र सूंघ]] राजा बनलें आ सुंघ वंश के स्थापना कीइलें। अशोक के बारे जादे तर बात शीलालेखन से पता लागेला जवन की [[प्राकृत भाषा]] में लिखल बा। == एतेहासिक स्त्रोत == अंग्रेजन के समय के इतिहासकार लगभग अशोक के भुला गइल रहन, बाकिर [[जेम्स प्रिंसपी]] ऐतिहासिक स्त्रोतन के फेनू से जोहे के काम कइलन। [[जॉन ह्यूबर्ट मार्शल]] एगो आउर महत्वपूर्ण इतिहासकार रहन। [[चित्र:AsokaKandahar.jpg|thumb|middle|अशोक के एगो शिलालेख]] अशोक के जीवन आ शासन के जानकारी जादे तर बुद्ध स्त्रोतन से मिलेला। अशोकवदन से अशोक के बारे में ढेर जानकारी मिल जाला,आउर जानकारी दीपवंश,महावंश आ [[अशोक के शिलालेख]] सभ से मीलेला। अशोक के घड़ी के चीज [[कुम्हार]],पटना मे मिलेला। अशोक के अध्यादेश:- अशोक के अध्यादेश 33 गो शिलालेखन के समूह बा। ई शिलालेख सभ [[भारत]] आ [[पाकिस्तान]] में फैलल बा। ई शिलालेख सभ भारत के इतिहास के एगो सभसे बरियार राजा द्वारा बुद्ध धरम के परचार के कहानी बतवेला। [[महावंश]]:- ई [[श्रीलंका]] के राजा लोग के लिखल एगो कविता हऽ जेकरा [[पाली]] भाषा में लिखल गइल बा। == धारना सभ आउर इतिहास लेखन == अशोक के जीवन के जानकारी फेनु से प्राप्त करे में सभ से जादे बुद्ध स्त्रोत के इस्तेमाल कइल गइल बा। पारंपरिक लेखन के आधार पऽ शुरुआती विद्वान अशोक के एगो बौद्ध सम्राट मनले बाड़न, जेकरा धर्म बदले के पड़ल रहे,आ जे बुद्ध धर्म के परचार कइले रहे। रोमिला थापर अशोक के बारे में लिखले बाड़ी की " हमनी के अशोक के दू तरह से देखे के चाहीं, पाहिला एगो राजनेता जवन एक समय में आपन साम्राज्य स्थापित कइलन, आ दूसरा एगो अइसन आदमी जे समाज के बदले खातिर आपन आचार विचार के समाज में फैलीईलें। अशोक अपना घड़ी के हर धर्म के इज्जत करत रहन। हालाकि शिलालेख से ई बात साफ पता लागेला की अशोक बुद्ध धर्म के मानत रहन। एगो शिलालेख में अशोक जानवर सभ के बली देवे वाला वैदिक अनुष्ठान के प्रतिबंधित कइले बाड़न, एकरा से ई बात साबित होखेला की ऊ वैदिक परंपरा के ना मानत रहन। ऊ बुद्ध धर्म के पवित्र जगहन पऽ आपन स्तंभ खड़ा करवईलें बाकिर बाकी सभ धर्म के पवित्र जगह पऽ स्तंभ ना बनववलन। अशोक कर्तव्यन के देखावे ला "धम्म" शब्द के इस्तेमाल कइलन,जवन एगो बौद्ध शब्द हऽ। साथे सारे ऊ बुद्ध धर्म के तीन गो उपदेशनो के परचार कइलन। अशोक के सभ शिलालेखन में अशोक के एगो निमन आ दयालु राजा देखावल गइल बा। कलिंग के शिलालेखन में अशोक अपना के आपन प्रजा के पिता कहले बाड़न आउर आपन प्रजा के आपन संतान बोलले बाड़ें।<ref>http://www.cs.colostate.edu/~malaiya/ashoka.html</ref> शिलालेखन में अशोक बुद्ध धरम के शिक्षण के लिखववले बाड़न आ अहींसा के अपनावे के कहले बाड़न, साथे साथे ऊ आपन राज के बारे मे बतवले बाड़ें । [[सारनाथ]] के अशोक स्तंभ अशोक के समय के सभ से प्रसिद्ध चीज हऽ। बलुआ पत्थर के बनल स्तंभ अशोक के सारनाथ यात्रा के बारे में बतावेला। स्तंभ के ऊपर चारो दिशा में चार गो शेर के मुह बनल बा जे अशोक के बड़हन आ बरियार राज्य के दर्शावेला। शिलालेखन से मिलल जादे तर जानकारी के इतिहासकार सही मानेलन, लेकिन ई बतावल मुश्किल बा की कुछ चीज साँचो भइल रहे की ना। अशोक के शिलालेख सभ के खोज से पहिले अशोक के बारे में ढ़ेर कहानी प्रचलित रहे जवन की ऐतिहासिक स्त्रोत पऽ आधारित ना रहे। ओह में से कुछ कहानी अशोकवदन में से बा:- 1) ओह में से एगो कहानी जय नाम के एगो लईका के हऽ, एक दिन जब जय सड़क के किनारे खेलत रहे, जहा बुद्ध आ गइलन।छोटहन जय बुद्ध केभीख के कटोरा में एक मुट्ठी माटी रख के आगे चल के बहुत बड़ राजा आ बुद्ध धर्म के अनुयाई बन के वरदान मंगलस।आगे लिखल बा की बुद्ध पूरा ब्रह्माण्ड के अंजोर कऽ दिहलन आ फिर ऊ अंजोरा के आपन बाया हाथ में लेके बोललन की ई लाईका आपन अगीला जनम में बहुत बड़ राजा बनी। बुद्ध ईहो भविष्यवाणी कइलें की हेकर राजधानी पाटलिपुत्र होई। 2)अगिला कहनी में अशोक के एगो खराब आदमी देखावल गइल बा, जे बुद्ध धरम अपनवला के बाद निक आदीमी बनऽता। लिखल बा कि अशोक के शारीरिक कुरूपता के चलते [[बिन्दुसा|बिन्दुसार]] ओकरा के पसंद ना करत रहन। अशोक राजा बनल चाहत रहन एही चलते उ वारिस के जरत कोयला से भरल गड्ढा में डाल दिहलन। उ आपन खराब स्वभाव आउर खिस के चलते "भयंकर अशोक" नाम से प्रसिद्ध भइलन। कहल जाला की उ आपन सैनीकन के वफादारी के परिक्षा खातीर ले गइल रहन, आ सफल ना होला पऽ 500 आदमी के मार दिहलन कहल जाला कि जब कुछ औरत लोग उनकर अपमान कइलस तऽ उ आपन हरम के जरा के मार दिहलन। मानल जाला कि उ लोगन के उदास देखिके आनंद उठावत रहन आउर एकरा खातिर उ एगो बड़हन आउर भयावह उत्पीड़न कक्ष बनईलन जेने उ आउर लोग के यातना देवत रहन। कहानी आगे ई बतावे ले ह कि कईसे एगो पवित्र बौद्ध भिक्षु से मिलला के बाद अशोक धार्मिक बन जा ताड़न। 3) एगो आउर कहानी मे कहल गइल बा की आपन खजाना के बौद्ध संघ उपहार देवल चालु कर देलन। मंत्री सभ के डर लागत रहे की एकरा से साम्राज्य के पतन हो जाई, ईहे चलते अशोक के मना करल गइल।फिर अशोक आपन निजी धन के उपहार मे देवे लगलन। अंत मे कुछ ना बाचल आ हुनकर शांति से निधन भइल। एह बिंदु पऽ इ ध्यान रखल ज़रूरी बा कि अशोकवदन एगो बौद्ध पाठ हऽ, जे बौद्ध धर्म मे नाया लोग जोड़े ला लिखल रहे आउर एही चलते इ सभ किंवदंतियन के इस्तेमाल कइल गइल रहे। एह तरह के ग्रंथन मे कहल गइल बा कि अशोक मूलतः आदर्श बौद्ध शासक रहन, जे प्रशंसा आउर अनुकरण दुनो के हकदार रहन। '''अशोक आउर बुद्ध के अवशेष''' बौद्ध कथा , विशेष रूप से [[महापरिनिर्वाण]], के अनुसार बुद्ध के अवशेष हुनकर मरला के बाद आठ देशों में साझा कइल गइल। अशोक अवशेष वापस लेवे के काम कइलन आउर 84000 स्तूपन में बांट देलन। ई कहानी सांची आउर भरहुत में स्पष्ट रूप से देखाईल गइल बा। == योगदान == [[चित्र:Sanchi2.jpg|thumb|middle|सांची के स्तूप]] एगो बौद्ध राजा होला के नाते,अशोक मानत रहन की बुद्ध धरम आदमी,जानवर, पौधा सभ जीव जंतु खातिर निक बा, एही चलते उ ढेरे स्तूप आ बौद्ध भिख्छु के रहे खातिर घर बनववलें। अशोकवदन में लिखल बा कि अशोक बुद्ध के अवशेष के रखे खातिर 84000 स्तूप बनाए के आदेश देले रहन। अशोक आपन बेटा [[महेंद्र]] आउर बेटी [[संघमित्रा]] के बौद्ध धर्म के परचार खातिर श्रीलंका भेजले रहन। ''' व्यवस्थापक के रूप में ''' [[चित्र:MauryanRingstone.JPG|thumb|middle|मौर्य काल के एगो पत्थर]] अशोक के सेना बरियार रहे, बाकिर बौद्ध धर्म के अपनवला के बाद, ऊ दक्षिण में तीन गो प्रमुख तमिल राज्य चेरस, चोल आ पंड्या के जोरे संघतिया नियन संबंध बनईलन। हुनकर शिलालेखन में कहल गइल बा कि ऊ आपने राज्य आ पड़ोसी राज्यन में लोग आउर जानवरन के चिकित्सा खातीर प्रावधान कइले बाड़न। उ आम लोगन के लाभ खातीर सड़कन पऽ ईनार खोदववले आ पेड़ लगववले बाड़े। ''' जानवर सभ के कल्यान ''' अशोक के शीलालेखन में ई बात साफ लिखल गइल बा की जानवर सभ के दुख देवल केनियो ले नीक काम नइखे।<ref>https://en.m.wikipedia.org/wiki/Ashoka#CITEREFFitzgerald2004</ref> हालाकि ऊ गाय के मांस खाए पऽ रोक ना लगवले रहन।<ref>https://books.google.co.in/books?id=JwGZTQunH00C&pg=PA108&redir_esc=y#v=onepage&q&f=false</ref> ऊ उ सभ चार गोड़ वाला जानवरन के मारे पऽ रोक लगा देले रहन जवना के ना कवनो इस्तेमाल रहे आउर ना खाईल जा सकत रहे। संगे-संगे कुछ बिशेष परजाति के चिड़ई आ जानवर के मारे पे रोक लगा देले रहन। एतने ना बलुक उ सभ मादा बकरी, सुअर आ भेड़ के मारे पऽ रोक रहे जवन आपन बच्चा के देखभाल करत होखे। अशोक जानवर के मरला पऽ रोक लगवले रहन आ ओखिनी के इलाज ला अस्पतालो बनववले रहन। अशोक के घड़ी के मौर्य साम्राज्य दुनिया के गीनल चुनल साम्राज्य में आवेला जे आपन जानवर के नागरिक नियन राखत रहे।<ref>https://en.m.wikipedia.org/wiki/Special:BookSources/1590561066</ref> ''' अशोक चक्र ''' [[चित्र:Ashoka Chakra.svg|thumb|middle|[[अशोक चक्र]]]] [[अशोक चक्र]] धर्मचक्र के एगो चित्रण बा। एह में चौबीस गो तिली होखला। अशोक चक्र के जगह-जगह बनवावल गइल रहे। आज-काल अशोक चक्र सभ से जादे भारत के झंडा पऽ लउकेला। अशोक चक्र के भारत के झण्डा के उजरका पट्टी पऽ आसमानी रङ्ग से बनावल गइल बा। अशोक चक्र के [[अशोक स्तंभ]] पे भी देखावल गइल बा। '''वास्तुकला ''' अक्सर अशोक के भारत में पत्थर पऽ वास्तुकला चालू करवाए के श्रेय दिहल जाला, जवन की संभवतः यूनानी सभ से सीखल गइल रहे।<ref>[https://books.google.co.in/books?id=fpfTAgAAQBAJ&pg=PT21&redir_esc=y#v=onepage&q&f=false a]</ref> अशोक से पहिले घर आ मकान अस्थाई समान से बनत रहे जईसे लकड़ी, [[बांस]] आ खपड़ा।<ref>[https://books.google.co.in/books?id=TlVeuxIgjwQC&pg=PA14&redir_esc=y#v=onepage&q&f=false सी]</ref> [[पाटलिपुत्र]] में अशोक आपन लकड़ी के महल के पत्थर के बंववलें।<ref>Ashoka was known to be a great builder who may have even imported craftsmen from abroad to build royal monuments." Monuments, Power and Poverty in India: From Ashoka to the Raj, A. S. Bhalla, I.B.Tauris, 2015 p.18</ref> ओह महल के बनवावे में उ बिदेसी कारीगर के इस्तेमाल कइले रहन। ''' अशोक स्तंभ ''' [[चित्र:Ashok Sthamba.jpg|thumb|middle|[[लुंबिनी]],[[नेपाल]](जहवाँ बुद्ध जन्मल रहन) में बनवावल स्तम्भ]] अशोक जगह जगह स्तंभ बनववले रहन। अईसे तऽ अशोक के ढ़ेरे स्तंभ होखे के चाहीँ लेकिन अभी खाली दसे गो बा। स्तंभ सभ के औसत ऊंचाई 40-50 फीट आ वजन 50,000 किलोग्राम बा। सँवसे स्तंभ सभ के बनारस में तरासल गइल रहे आ फिर जगह जगह ले जा के खड़ा कइल गइल रहे।स्तंभ सभ के खोज सोरहवाँ शताब्दी में थॉमस कॉर्यात कइलें रहन। ''' सिंह चतुर्भुज स्तंभ शीर्ष ''' [[चित्र:Sarnath capital.jpg|thumb|middle|भारत के राष्ट्रीय चिह्न]] सिंह चतुर्भुज स्तंभ शीर्ष अशोक के [[सारनाथ]] के स्तंभ पऽ धईल रहे, बाकिर अब उ [[सारनाथ]] संग्रहालय में धईल बा।एकरा भारत के राष्ट्रीय चिन्ह के रूप में भी अपनावल गइल बा। == कला, चलचित्र आ साहित्य में == [[चित्र:Asoka's Queen.jpg|thumb|middle|[[अबनिन्द्रनाथ टैगोर]] के बनावल चित्र जेमे अशोक के रानी के सांची के स्तूप भिरी खड़ा देखावल गइल बा।]] • [[जयशंकर प्रसाद]] 'अशोक की चिंता' (अशोक के चिंता) नाम के एगो कविता लीखले बाड़े, जेह में [[कलिंग]] युद्ध घड़ी अशोक के विचार लिखल गइल बा। • 1941 में [[अशोक कुमार]] नाम के तमिल फिल्म बनवाल गइल रहे जवन अशोक के बारे में रहे। • 1973 में [[अमर चित्रकथा]] अशोक के जीवन पऽ एगो किताब प्रकाशित कइले रहे। • 2001 में अशोक नाम के एगो फिल्म बनल रहे जेह में [[शाहरुख खान]] काम कइले रहन। == इहो देखल जाय == * [[अशोक के आदेसलेख]] == संदर्भ == {{Reflist|35em}} == स्रोत सामग्री == * {{citation |last=Singh |first=Upinder |authorlink=Upinder Singh |title=A history of ancient and early medieval India : from the Stone Age to the 12th century |date=2008 |publisher=[[Pearson Education]] |location=[[New Delhi]] |isbn=978-81-317-1120-0|url=https://books.google.co.in/books?id=GW5Gx0HSXKUC |ref={{sfnref|Upinder Singh|2008}} }} == बाहरी कड़ी == {{Commons category|Ashoka|अशोक}} {{wikiquote}} * BBC Radio 4: Sunil Khilnani, ''[http://www.bbc.co.uk/programmes/b05tpy82 Incarnations: Ashoka.]'' * BBC Radio 4: Melvyn Bragg with Richard Gombrich et al., ''[http://www.bbc.co.uk/programmes/b0511tm1 In Our Time, Ashoka the Great]''. * {{Cite book|title = Inscriptions of Asoka: New Edition|last = Hultzsch|first = E.|publisher = Government of India|year = 1925|isbn = |location = Oxford|pages = |url = https://archive.org/details/InscriptionsOfAsoka.NewEditionByE.Hultzsch}} {{S-start}} {{S-hou|[[मौर्य वंश]]||304 BCE||232 BCE}} {{S-bef|before=[[बिंदुसार]]}} {{S-ttl|title=[[मौर्य साम्राज्य|मौर्य सम्राट]] |years=272–232 BCE}} {{S-aft|after=[[दशरथ मौर्य|दशरथ]]}} {{s-end}} {{Buddhism topics}} {{Authority control}} [[श्रेणी:3री-सदी ईसापूर्ब के भारतीय राजा]] [[श्रेणी:मौर्य सम्राट]] [[श्रेणी:अशोक| ]] ou7z71dtv9akehpfyb4s47cg5frweui मथुरा 0 37477 803297 785212 2026-07-29T16:06:40Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803297 wikitext text/x-wiki {{Infobox settlement | name = मथुरा | native_name = | native_name_lang = <!-- ISO 639-2 code e.g. "fr" for French. If more than one, use {{lang}} instead --> | settlement_type = [[शहर]] | image_skyline = | image_alt = | image_caption = | etymology = | nickname = | population_as_of = 2011 | population_footnotes = | population_total = 454937 | latd = | longd = | coordinates_type = | coordinates_region = | coordinates_display = inline,title | subdivision_type = देस | subdivision_name = {{flag|भारत}} | subdivision_type1 = [[भारत के राज्य अउरी संघ राज्यक्षेत्र|राज्य]] | subdivision_name1 = [[उत्तर प्रदेश]] | subdivision_type2 = | subdivision_name2 = | website = <!-- {{URL|example.com}} --> }} '''मथुरा''' ({{Lang|en|[[:en:Mathura|Mathura]]}}) भारत के [[उत्तर प्रदेश]] राज्य में एक ठो शहर बा।मथुरा एगो इतिहासी एवं धार्मिक पर्यटन स्थल की रूप में प्रसिद्ध बा। लंबा समय से मथुरा प्राचीन भारतीय संस्कृति एवं सभ्यता क केंद्र रहल बा। भारतीय धर्म, दर्शन कला एवं साहित्य की निर्माण तथा विकास में मथुरा का महत्वपूर्ण योगदान सदा से रहल बा। आजो महाकवि सूरदास, संगीत क आचार्य स्वामी हरिदास, स्वामी दयानंद क गुरु स्वामी विरजानंद, कवि रसखान आदि महान हस्तीयन से ए नगरी क नाम जुड़ल बा। मथुरा के श्रीकृष्ण जन्म भूमि की नांव से भी जानल जाला।<ref name=Prasad>[https://books.google.com/books?id=o0_5caqiUH0C&pg=PT22&dq=Krishna+Janmasthan&hl=en&sa=X&ved=0ahUKEwj1-6mYj6XOAhUBEz4KHTL5CTcQ6AEIHjAA#v=onepage&q=Krishna%20Janmasthan&f=false Krishna: A Journey through the Lands & Legends of Krishna], by Dev Prasad</ref> [[File:*Bindrabun -Vrindavan-. Gate of Shet Lukhmeechund's Temple; a photo by Eugene Clutterbuck Impey, 1860's.jpg|thumb|upright|Gate of Shet Lukhmeechund's Temple, a photo by Eugene Clutterbuck Impey, 1860's.]] ==मथुरा क परिचय== [[File:Great Emperor Kanishka - Greatest of Kushan Monarchs - Circa 1st Century CE - Mathura - Government Museum - Mathura 2013-02-23 5836.JPG|thumb|upright|Sculpture from 1st century CE on display at Mathura museum.]] मथुरा, भगवान कृष्ण क जन्मस्थली और भारत क परम प्राचीन तथा जगद्-विख्यात नगरी ह। शूरसेन देश क एइजा राजधानी रहे।<ref name=ImpGazetteer>{{cite web|title=Imperial Gazetteer of India. v. 18.|url=http://dsal.uchicago.edu/reference/gazetteer/toc.html?volume=18|work=Digital South Asia Library|pages=63–74|year=1908|access-date=2017-06-16|archive-date=2017-08-26|archive-url=https://web.archive.org/web/20170826213103/http://dsal.uchicago.edu/reference/gazetteer/toc.html?volume=18|url-status=dead}}</ref> पौराणिक साहित्य में मथुरा के अनेक नांव से संबोधित कइल गइल बा जैसे- शूरसेन नगरी, मधुपुरी, मधुनगरी, मधुरा आदि। भारतवर्ष क उ भाग जवन  हिमालय और विंध्याचल की बीच में पड़ेला ओके प्राचीनकाल में आर्यावर्त कहल जाव। एइजा पनपल भारतीय संस्कृति के जवन धारा सींचली स उ गंगा और यमुना क धारा रहली स। ए दुनुं नदी की किनारे भारतीय संस्कृति क कईगो केन्द्र बनन स और विकसित भई न स। वाराणसी, प्रयाग, कौशाम्बी, हस्तिनापुर,कन्नौज आदि केतना ऐसन स्थान हंव स, परन्तु इ तालिका तबले पूर्ण नाहीं हो सकेले जबले एमें मथुरा क समावेश न कइल जाव। इ आगरा से दिल्ली की ओर और दिल्ली से आगरा की ओर क्रमश: 58 किलोमीटर उत्तर-पश्चिम एवं 145 किलोमीटर दक्षिण-पश्चिम में यमुना की किनारे राष्ट्रीय राजमार्ग 2 पर स्थित बा। वाल्मीकि रामायण में मथुरा के मधुपुर चाहें मधुदानव क नगर कहल गइल बा तथा एइजा लवणासुर क राजधानी बतावल गइल बा- ए नगरी के ए प्रसंग में मधुदैत्य द्वारा बसावल, बतावल गइल बा। लवणासुर, जेके शत्रुघ्न युद्ध में हरा के मारले रह न उ एही मधुदानव क पुत्र रह न। ==जनसांख्यिकी== साल 2011 के जनगणना<ref name="census2011">{{cite web|title=City Census 2011|url=http://www.census2011.co.in/city.php|website=census2011.co.in|accessdate=22 नवंबर 2016}}</ref> अनुसार मथुरा के कुल शहरी जनसंख्या 349909 बाटे आ आसपास के उपशहरी इलाका के मिला लिहल जाय तब कुल जनसंख्या 454937 बा। जनसंख्या के हिसाब से ई भारत के 125वाँ शहर बाटे। जनगणना आँकड़ा के मोताबिक एह शहर में [[लिंगानुपात]] 881 आ [[साक्षरता दर|साक्षरता के दर]] 74.97% बाटे।<ref name="census2011" /> {{clear}} ==संदर्भ== {{reflist}} ==बाहरी कड़ी== {{Commons category|Mathura}} * [http://www.mathuracity.in Mathura City E-Portal] {{Webarchive|url=https://web.archive.org/web/20200229100541/http://www.mathuracity.in/ |date=2020-02-29 }} * [http://www.palikanon.com/english/pali_names/ma/madhuraa.htm Entry on Mathura in the Dictionary on Pali Proper Names] * [http://hridayindia.in/mathura/ Mathura HRIDAY city] [[श्रेणी:भारत के शहर आ कस्बा]] [[श्रेणी:उत्तर प्रदेश के शहर]] [[श्रेणी:पबित्र हिंदू नगर]] {{India-geo-stub}} klo5pjjbg22qwb801b7t04h5a6zyk29 शेर शाह सूरी 0 38905 803336 765865 2026-07-29T16:21:42Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803336 wikitext text/x-wiki {{Infobox royalty | title = पादिशाह | image = | caption = शेर शाह के कल्पानिक छावि, अब्दुल गफूर के बनावल | succession = [[सूरी|सुर कुल कर सुलतान]] | reign = 17 मई 1538 – 22 मई1545 | coronation = 1540 | predecessor = [[हुमायूँ]] | successor = इस्लाम शाह सूरी | spouse = उत्मादुन निस्सा बानो बेगम रानी शाह बेगम | issue = इस्लाम शाह सूरी (जलाल खान)<br /> अदिल खान | full name = फरिद खान लोधी | house = | dynasty = सुर साम्राज्य | father = हसन खान सूरी | birth_date = 1486 | birth_place = [[सासाराम]], [[दिल्ली सल्तनत]] (अखनी [[बिहार]] मे, [[भारत]]<ref name="Sarwani"/>{{npsn|date=December 2016}} | death_date = 22 मई 1545 (उमिर 58–59) | death_place = कालिंजर | place of burial = [[शेर शाह सुरी के मकबरा]], सासाराम | religion = [[इस्लाम]] |}} '''शाह सूरी'''(𑂬𑂵𑂩 𑂬𑂰𑂯 𑂮𑂴𑂩𑂲) (1486 - 22 मई 1545) [[उत्तर भारत]] में सूरी साम्राज्य के संस्थापक रहलें। एह साम्राज्य के राजधानी [[सासाराम]] रहल। शेर शाह एक मूल रूप से पख्तून अफगानी रहलें आ 1540 में मुगलन से सत्ता हासिल कइलेन। एक ठो दुर्घटना में उनके मौत के बाद उनके लड़िका इस्लाम शाह सत्ता पवलें। राजा बने से पहिले ई मुगल सेना मे एगो सपिही रहन, फेर ई [[बाबर]] के पूत [[हुमायूँ]] के हरा के अपने राजा बन जा तताड़ऽन। आपन सात बछर के राज मे ई बहुते नीमन नीमन काज कइलन आ परिवर्तन कइलन। हीनकर कुछु कुछु काज के मुगल राजा लोग, जइसे [[अकबर]] आगे बढ़वलस। ई हुमायूँ के ''दिना पनाह'' नगर के बिकास कइलन आ होकर नांव शेरगढ़ धइलन आ [[पाटलिपुत्र]] जेेकर सतमा सदी मे ओरा गइल रहे होके फेर से थापित कइलन। ==जिनगी== शेर शाह सूरी एगो पशतुन कुल मे [[सासाराम]] जनमल रहन, जेवन की अबही [[बिहार]] मे बाटे।<ref>Asad Muḥammad K̲h̲ān̲, ''The Harvest of Anger and Other Stories'', Oxford University Press (2002), p. 62</ref><ref>[[Ishwari Prasad]], ''The Mughal Empire'', Chugh Publications (1974), p. 157</ref> हिनकर उपनाँव पश्तुन के सुर जात से लिहल बाटे। कहल जाला जे, हीनकर नांव ''शेर'' एह से पड़ल काहे कि ई जहिया सेयान रहन तहिया एक हाली एगो बाघ से लड़ल रहन आ होके मुवा दिहले रहन।<ref name=":0">{{cite book |last1=Lane-Poole |first1=Stanley |author-link=Stanley Lane-Poole |year=2007 |orig-year=First published 1903 |title=Medieval India: under Mohammedan rule (A.D. 712-1764) |publisher=Sang-e-Meel Publications |page=236 |isbn=978-969-35-2052-1}}</ref><ref name=EBSur>{{cite web|title=Sur Dynasty|url=http://www.britannica.com/topic/Sur-dynasty|website=Encyclopædia Britannica|access-date=5 December 2015}}</ref> हिनकर आजा इब्राहिम खान सुरी एगो जागीरदार रहन। ई मियां हसन खान सुरी के आठ गो पुत मे से एगो रहन। लईकाई मे फरीद आपन घरे से भाग गइलन, जब हुनकर पिता, हसन खान के ई बात पता लगाल जे उ [[जौनपुर]] के राजा जमाल खान लगे गइल बाड़न तऽ ऊ जमाल के एगो पाती लीखलन आ कहलन: {{quote|फरीद खान, हमरा से खिसिया के, बिना केवनो कारन के रउरा लगे चलि गइल बाड़ें। हमरा रउवा प पूरा बिस्वास बा जे रउवा हुनका के मना लिहब, आ बिदा कऽ दिहब; बाकिर जदि उ राऊर बाति नइखन सुनत, आ नइखन आवत, तऽ हमरा बिस्वास बा जे रउवा हुनकरा अपना भीरी धऽ लिहब, हम आसा करब जे ओहिजा हुनका धरम के सिख मिली<ref name="Sarwari-79">{{cite web |url=http://persian.packhum.org/persian/pf?file=80201014&ct=79|title=Táríkh-i Sher Sháhí; or, Tuhfat-i Akbar Sháhí, of 'Abbás Khán Sarwání. CHAPTER I. Account of the reign of Sher Sháh Súr.|page=79|author=Abbas Khan Sarwani|work=Sir H. M. Elliot|location=London|publisher=[[Packard Humanities Institute]]|year=1580|access-date=4 September 2010|author-link=Tarikh-i-Sher Shahi}}</ref>}} जमाल खान, फरीद से घरे जाए के कहलन बाकिर फरीद ई बात ना सकरलें, एगो दोसर पाती मे उ लिखलन: {{quote|जदि हमार बाबूजी चाहऽतानी जे हम सिक्खा लिहीं, तऽ एह नगर मे ढेर पढ़ल लिखल जन बा: हम एहिजा पढ़ब<ref name="Sarwari-79"/>}} ==बिहार आ बंगाल के बीजऽय== {{Quote box|quote= शेर शाह मलक (फरिश्ता) ना रहन बलुक मालिक (राजा) रहन। छव बछर मे, ऊ राज के स्थाई कइलन जेकर नेंव आजुओ बाचल बा। हुनकर भारत मे कइल काज इरान आ तुरान के राजा लोग मन के भा गइल आ ऊ लोग एकरा देखल चाहेला। हज़रत अर्श ऐशियानी (अकबर) हुनकरे कइल काज (ज़वाबित) के अगिला पचास बछल रहे देलन आ ओके कोनो बदलाव ना कइलन। आजु उहे भारत मे बन छोड़ि के कुछुवो नइखे बाचल।|source=मिर्जा आजिज़ कोका, अताग खान के पूत, के लिखल [[जहाँगीर]] लगे एगो पाती|align=centre|width=100%|fontsize=85%|quoted=1}} शेर शाह बिहार के मुगल प्रशासक, बहर खान लोहनी, ला काम कइल चालू कइलन।<ref name=Britannica/><ref name=bpedia>{{cite book |last=अली |first=मुहम्मद अंसार |year=2012 |chapter=शेर शाह |chapter-url=http://en.banglapedia.org/index.php?title=Sher_Shah |editor1-last=Islam |editor1-first=Sirajul |editor1-link=Sirajul Islam |editor2-last=Jamal |editor2-first=Ahmed A. |title=Banglapedia: National Encyclopedia of Bangladesh |edition=Second |publisher=[[Asiatic Society of Bangladesh]]}}</ref> हुनकर बीरता के चलते बहर खान हुनकरा शेर खान कहत रहन। बहर खान के मुवला के बाद, शेर खान, जमाल खान के प्रतिनिधी राजा बनलन। बादि मे शेर खान के बढ़त तागत के देखि के जमाल खान बंगाल के सुल्तान गियासुद्दीन मुहम्मद शाह के मदद मंगलन। गियासुद्दीन शाह, इब्राहिम खान के नेतृत्व मे आपन सेना भेजलन, बकीर शेर खान उज्जैनीया राजपूतन संघे मिली के, 1534 मे, सूरजगढ़ के जुध मे ओह सेना ले जीत गइलन।<ref>{{cite book |last=Ahmad |first=Imtiaz |year=2008 |chapter=State Formation and Consolidation under the Ujjaniya Rajputs |editor1=Surinder Singh |editor2=Ishawr Dayal Gaur |title=Popular Literature and Pre-modern Societies in South Asia |chapter-url=https://books.google.com/books?id=QVA0JAzQJkYC&pg=PA80 |publisher=Pearson Education India |page=80 |isbn=978-81-317-1358-7}}</ref> एकरा बादि बिहार प हुनकर अधिकार हो जाता। 1538 मे शेर खान, बंगाल प आक्रमन कइलन आ मुहम्मद शाह के हरा देलन, बाकिर हुमायूँ के चलते ओह राज प ऊ कब्जा ना कई पइलन। 26 जुन 1539 के, शेर खान आउर [[हुमायूँ]] मे [[चउसा के जुध]] भइल जेहमे शेर खान जई भइलन। मई 1540 मे शेर खान, हुमायूँ के फेर कन्नौज मे हरइलन आ हुमायूँ के भारत छोरे पड़ल।<ref name=Britannica /><ref name="Wolseley Haig">{{cite book |last1=Haig |first1=Wolseley |author-link1=Wolseley Haig |year=1962 |orig-year=First published 1937 |chapter=Sher Shah and the Sur Dynasty |chapter-url=https://books.google.com/books?id=yoI8AAAAIAAJ&pg=PA51 |editor1-last=Burn |editor1-first=Richard |editor1-link=Richard Burn (Indologist) |title=The Cambridge History of India |publisher=Cambridge University Press |volume=Volume IV: The Mughal Period |page=51 |access-date=16 November 2016 |language=en}}</ref> ==मालवा के बीजऽय== 1537 मे गुजरात के बहादुर शाह मे मूवला के बादि, मालवा कर नवका राजा कादिर शाह बनलन। ==मारवाड़ के बीजऽय== 1543 मे शेर शाह सूरी एगो 80,000 लड़ाका वाला बिसाल सेना लेके मरवाड़ के राजा मालदेव राठौर से जुध कइलन आ हुनकरा हरा देलन आ जोधपुर प अधिकार हासिल कई लिहलन। ==राज काज== [[File:An inspection of Sher Shah Sur's Great North Road.jpg|thumb|एगो राह के जायजा लेत शेर शाह सूरी]] [[File:Sher shah's rupee.jpg|thumb|right|शेर शाह सूरी के रूपिया]] शेर शाह सूरी तीन परकार के सिकका चलवलें। शेर शाह से पहिले रूपिया चानी के सिक्का के कहल जात रहे बाकिर शेर शाह सूरी, एक रूपिया मे लागे वाला चानी के भार के निश्चित (178 ग्राम) कई दिहलन, एही रूपिया आजो भारत के मुद्रा बाटे।<ref name="RBI">{{cite web |url=http://www.rbi.org.in/currency/museum/c-mogul.html |title=Mughal Coinage |website=[[RBI Monetary Museum]] |publisher=Reserve Bank of India |access-date=24 August 2010 |url-status=dead |archive-url=https://web.archive.org/web/20080516085855/http://www.rbi.org.in/currency/museum/c-mogul.html |archive-date=16 May 2008 }}</ref> रूपिया [[भारत]] संघे [[नेपाल]], [[इंडोनेशिया]], [[मालदीव]], [[मॉरिशस]], [[पाकिस्तान]] आ [[श्रीलंका]] के मुद्रा बाटे। सोना के सिक्का मुहर कहात रहे आ होकर भार 169 ग्राम होखत रहे आ तामा के सिक्का के पईसा कहल जात रहे। शेर शाह [[जी.टी.रोड]] के फेर से बनवलन आ होके राहे आजम कहलन जवन अफगान से बांग्लादेश के जोड़त रहे। ==छबि सभ== <gallery> Lal Darwaza or Sher Shah Gate, with ruins along approach.jpg|लाल दरवाजा Rohtas Fort Magnificent Kabuli Gate.jpg|रोहतास के किला के काबुली दुआर Qila-i-Kohna.jpg|किलाई कुहना महजीद, 1541 मे शेर शाह सूरी के बनवावल Sher Mandal, Purana Qila.jpg|शेर शाह के ईयाद मे मुगल सभ के बनवावल शेर मंडल Copper Dam of Sher Shah Suri, issued from Narnul mint.jpg|नारनौल टक्साल के तामा के दाम </gallery> ==सनर्भ== [[श्रेणी:भारत के इतिहास]] [[श्रेणी:मध्यकालीन भारत]] dvw0lpxk7x05bkr6vxr6ae19dysjqa0 Module:Separated entries 828 38938 803567 634468 2018-12-17T20:54:33Z en>Amorymeltzer 0 Changed protection level for "[[Module:Separated entries]]": [[WP:High-risk templates|High-risk Lua module]]: Over 2M transclusions ([Edit=Require administrator access] (indefinite) [Move=Require administrator access] (indefinite)) 634467 Scribunto text/plain -- This module takes positional parameters as input and concatenates them with -- an optional separator. The final separator (the "conjunction") can be -- specified independently, enabling natural-language lists like -- "foo, bar, baz and qux". The starting parameter can also be specified. local compressSparseArray = require('Module:TableTools').compressSparseArray local p = {} function p._main(args) local separator = args.separator -- Decode (convert to Unicode) HTML escape sequences, such as "&#32;" for space. and mw.text.decode(args.separator) or '' local conjunction = args.conjunction and mw.text.decode(args.conjunction) or separator -- Discard values before the starting parameter. local start = tonumber(args.start) if start then for i = 1, start - 1 do args[i] = nil end end -- Discard named parameters. local values = compressSparseArray(args) return mw.text.listToText(values, separator, conjunction) end local function makeInvokeFunction(separator, conjunction, first) return function (frame) local args = require('Module:Arguments').getArgs(frame) args.separator = separator or args.separator args.conjunction = conjunction or args.conjunction args.first = first or args.first return p._main(args) end end p.main = makeInvokeFunction() p.br = makeInvokeFunction('<br />') p.comma = makeInvokeFunction(mw.message.new('comma-separator'):plain()) return p r3n53qz4zycs3vxyo5ms9qjo1jm32bp 803568 803567 2024-11-09T16:07:13Z en>Pppery 0 Copy from sandbox per request 803568 Scribunto text/plain -- This module takes positional parameters as input and concatenates them with -- an optional separator. The final separator (the "conjunction") can be -- specified independently, enabling natural-language lists like -- "foo, bar, baz and qux". The starting parameter can also be specified. local compressSparseArray = require('Module:TableTools').compressSparseArray local p = {} function p._main(args) local dataPlural = args.dataPlural local separator = args.separator -- Decode (convert to Unicode) HTML escape sequences, such as "&#32;" for space. and mw.text.decode(args.separator) or '' local conjunction = args.conjunction and mw.text.decode(args.conjunction) or separator -- Discard values before the starting parameter. local start = tonumber(args.start) if start then for i = 1, start - 1 do args[i] = nil end end -- Discard named parameters. local values = compressSparseArray(args) local result = mw.text.listToText(values, separator, conjunction) if dataPlural and #values > 0 and (#separator > 0 or #conjunction > 0) then -- flag to [[Module:Detect singular]] whether this is a list with >1 element result = result..'<span style="display:none" data-plural="'..(#values > 1 and '1' or '0')..'"></span>' end return result end local function makeInvokeFunction(separator, conjunction, first) return function (frame) local args = require('Module:Arguments').getArgs(frame) args.separator = separator or args.separator args.conjunction = conjunction or args.conjunction args.first = first or args.first return p._main(args) end end p.main = makeInvokeFunction() p.br = makeInvokeFunction('<br />') p.comma = makeInvokeFunction(mw.message.new('comma-separator'):plain()) return p 0tp4y1kozxhpcw1ykqbo8e2gq604ky9 803569 803568 2025-10-13T16:33:18Z en>Ahecht 0 {frameOnly = (frame.args.frameOnly or '') ~= ''} 803569 Scribunto text/plain -- This module takes positional parameters as input and concatenates them with -- an optional separator. The final separator (the "conjunction") can be -- specified independently, enabling natural-language lists like -- "foo, bar, baz and qux". The starting parameter can also be specified. local compressSparseArray = require('Module:TableTools').compressSparseArray local p = {} function p._main(args) local dataPlural = args.dataPlural local separator = args.separator -- Decode (convert to Unicode) HTML escape sequences, such as "&#32;" for space. and mw.text.decode(args.separator) or '' local conjunction = args.conjunction and mw.text.decode(args.conjunction) or separator -- Discard values before the starting parameter. local start = tonumber(args.start) if start then for i = 1, start - 1 do args[i] = nil end end -- Discard named parameters. local values = compressSparseArray(args) local result = mw.text.listToText(values, separator, conjunction) if dataPlural and #values > 0 and (#separator > 0 or #conjunction > 0) then -- flag to [[Module:Detect singular]] whether this is a list with >1 element result = result..'<span style="display:none" data-plural="'..(#values > 1 and '1' or '0')..'"></span>' end return result end local function makeInvokeFunction(separator, conjunction, first) return function (frame) local args = require('Module:Arguments').getArgs(frame, {frameOnly = (frame.args.frameOnly or '') ~= ''}) args.separator = separator or args.separator args.conjunction = conjunction or args.conjunction args.first = first or args.first return p._main(args) end end p.main = makeInvokeFunction() p.br = makeInvokeFunction('<br />') p.comma = makeInvokeFunction(mw.message.new('comma-separator'):plain()) return p d3q6xup28j3xpu77wa8xwjjm6j0xizq 803570 803569 2026-07-30T00:17:49Z SM7 3953 3 revisions imported from [[:en:Module:Separated_entries]] 803569 Scribunto text/plain -- This module takes positional parameters as input and concatenates them with -- an optional separator. The final separator (the "conjunction") can be -- specified independently, enabling natural-language lists like -- "foo, bar, baz and qux". The starting parameter can also be specified. local compressSparseArray = require('Module:TableTools').compressSparseArray local p = {} function p._main(args) local dataPlural = args.dataPlural local separator = args.separator -- Decode (convert to Unicode) HTML escape sequences, such as "&#32;" for space. and mw.text.decode(args.separator) or '' local conjunction = args.conjunction and mw.text.decode(args.conjunction) or separator -- Discard values before the starting parameter. local start = tonumber(args.start) if start then for i = 1, start - 1 do args[i] = nil end end -- Discard named parameters. local values = compressSparseArray(args) local result = mw.text.listToText(values, separator, conjunction) if dataPlural and #values > 0 and (#separator > 0 or #conjunction > 0) then -- flag to [[Module:Detect singular]] whether this is a list with >1 element result = result..'<span style="display:none" data-plural="'..(#values > 1 and '1' or '0')..'"></span>' end return result end local function makeInvokeFunction(separator, conjunction, first) return function (frame) local args = require('Module:Arguments').getArgs(frame, {frameOnly = (frame.args.frameOnly or '') ~= ''}) args.separator = separator or args.separator args.conjunction = conjunction or args.conjunction args.first = first or args.first return p._main(args) end end p.main = makeInvokeFunction() p.br = makeInvokeFunction('<br />') p.comma = makeInvokeFunction(mw.message.new('comma-separator'):plain()) return p d3q6xup28j3xpu77wa8xwjjm6j0xizq प्रयोगकर्ता वार्ता:LasseKeskinen 3 41099 803284 567581 2026-07-29T13:26:34Z Ternarius 20240 Ternarius पन्ना [[प्रयोगकर्ता वार्ता:Roopeank]] के [[प्रयोगकर्ता वार्ता:LasseKeskinen]] पर स्थानांतरण कइलें: प्रयोगकर्ता के नाँव बदलाव के दौरान पन्ना "[[Special:CentralAuth/Roopeank|Roopeank]]" से "[[Special:CentralAuth/LasseKeskinen|LasseKeskinen]]" पर ऑटोमेटिक रूप से स्थानांतरित भइल। 567581 wikitext text/x-wiki {| id="GeoPort-upper" width="100%" cellpadding="5" cellspacing="6" style="background:#FFFAFF; text-align: justify; border-style:ridge; border-width:1px; border-color:#A9A9A9;" |- |<div style="float:right;width:30%"><small>तुरंत भइल बदलाव देखीं</small><br/>&nbsp;&nbsp;&nbsp;[[File:View recent changes.svg|45px|link=विशेष:RecentChanges]]<br/></div> <h1 style="display:inline-block;margin-top:.1em; text-align:right; margin-bottom:.2em; border-bottom:0; font-weight:bold;">Welcome! स्वागतम्! [[File:Crystal Clear app ksmiletris.png|25px]]&nbsp;</h1> राउर बहुत-बहुत स्वागत बा '''{{BASEPAGENAME}}''' जी ! {{#if: | {{{1}}} | }}<br/> <div style="float:right; <!--background:#F5F5DC;--> width:30%"> <!-- दाहिना साइडबार --> <big>'''ई जरूर पढ़ल जाय:'''</big> {| border="5" cellspacing="10" cellpadding="5" height="50" align=center border=0 style="background: transparent" |- | style="background-color:#FFFFFF; border: solid 2px #FFFFFF; padding:10px 20px;" | [[विकिपीडिया:विकिपीडिया का ना हवे|विकिपीडिया का ना हवे?]] |- | style="background-color:#FFFFFF border: solid 2px #FFFFFF; padding:10px 20px;" | [[विकिपीडिया:भोजपुरी में कइसे टाइप करब?|भोजपुरी में टाइपिंग]] |- | style="background-color:#FFFFFF; border: solid 1px #FFFFFF; padding:10px 20px;" | [[विकिपीडिया:सत्यापन जोग|प्रमाणित बात लिखीं]] |- | style="background-color:#FFFFFF; border: solid 1px #FFFFFF; padding:10px 20px;" | [[मदद:फुटनोट|संदर्भ कइसे जोड़ीं?]] |- | style="background-color:#FFFFFF; border: solid 1px #FFFFFF; padding:10px 20px;" | [[विकिपीडिया:नीति अउरी दिसानिर्देस|विकिनीति आ निर्देश]] |}</div> <!-- मुख्य पाठ --> '''{{#if: | {{{1}}} | {{BASEPAGENAME}} }} जी''', एह समय रउँआ [[विकिमीडिया फाउन्डेशन]] के परियोजना [[भोजपुरी]] [[विकिपीडिया]] पर बाड़ीं। भोजपुरी विकिपीडिया एगो मुक्त डिजिटल [[ज्ञानकोश]] हवे, जेवन अइसन भइया-बहिनी लोग मिल के लिखले बा जे ज्ञान बाँटे में बिस्वास करत बाटे। एह समय ए परियोजना में [[विशेष:ActiveUsers|{{NUMBEROFUSERS}} सदस्य]] लोग शामिल बाटे। ई बहुते खुशी क बाति बा कि रउँओं ए में शामिल हो गइल बाड़ीं। * पहिले से बनल [[विकिपीडिया:लेख|लेखवन]] में कौनो संपादन खाली टेस्ट करे खातिर मत करीं। कौनों तरह के परीक्षण <small>(प्रयोग या टेस्टिंग)</small> [[विकिपीडिया:अभ्यास पन्ना|अभ्यास पन्ना]] या [[Special:MyPage/sandbox|अपना अभ्यास पन्ना]] पर करीं। * [[विकिपीडिया:आपन परिचय कइसे देईं?|आपन परिचय]] आप संछेप में [[प्रयोगकर्ता:{{BASEPAGENAME}}|अपना सदस्य पन्ना]] पर दे सकत बानी। बहुत पर्सनल बात इहाँ मत लिखीं, न कौनों परचार वाली बात लिखीं। अपने खुद के बारे में लेख मत बनाईं। * दुसरा [[विकिपीडिया:चौपाल |सदस्य लोगन से बात]] करत समय, [[मदद:वार्ता पन्ना|बातचीत पन्ना]] पर सनेसा लिखले की बाद आपन [[विकिपीडिया:दसखत|दसखत]] <small>(हस्ताक्षर)</small> जरूर करीं। एकरा खातिर अंत में चार गो टेढ़का डैश (<nowiki>~~~~</nowiki>) लिख देंईं या टूलबार में [[File:Insert-signature2.svg|link=|alt=]] पर क्लिक करीं। * मदद चाहत होखीं त विकिपीडिया के [[विकिपीडिया:मदद|मदद पन्ना]] पर जाईं। <!-- फुटर के कड़ी सभ  --> सीखे-समझे खातिर कुछ अउरी कड़ी नीचे दिहल जात बाटे: {| border="5" cellspacing="1" cellpadding="0" height="50" align=center border=0 style="background: transparent" | style="background-color:#FFFFFF; border: solid 2px #F2BDCD; padding:1px 10px;" | [[विकिपीडिया:स्वशिक्षा|शुरू से सीखीं]] |&nbsp;&nbsp; | style="background-color:#FFFFFF; border: solid 2px #F2BDCD; padding:1px 20px;" | [[मदद:संपादन|संपादन सीखीं]] |&nbsp;&nbsp; | style="background-color:#FFFFFF; border: solid 2px #F2BDCD; padding:1px 20px;" | [[विकिपीडिया:नया लेख कइसे सुरू करीं?|नया लेख]] | style="background-color:#FFFFFF; border: solid 2px #F2BDCD; padding:1px 20px;" | [[विकिपीडिया:अइसन लेख मना बाटे|लेख मनाहीं]] |&nbsp;&nbsp; | style="background-color:#FFFFFF; border: solid 2px #F2BDCD; padding:1px 20px;" | [[विकिपीडिया:पंचशील|पंचशील]] | style="background-color:#FFFFFF; border: solid 2px #F2BDCD; padding:1px 20px;" | [[विकिपीडिया:समुदाय पोर्टल|सदस्य समाज पन्ना]] |} |} -- [[प्रयोगकर्ता:नया सदस्य स्वागतकर्ता|नया सदस्य स्वागतकर्ता]] ([[प्रयोगकर्ता वार्ता:नया सदस्य स्वागतकर्ता|बात करीं]]) ०६:२९, २८ मार्च २०१७ (UTC) 5zpw1grl8axk28magx1qppe20lhjmuo इंटरसिटी एक्सप्रेस 0 46266 803343 774664 2026-07-29T16:22:14Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803343 wikitext text/x-wiki '''इंटरसिटी एक्सप्रेस''' भारत में क एक्सप्रेस-ट्रेन सेवा ह जवन की प्रमुख रेलवे जंक्शन और राज्य की राजधानियन के जोड़ेला। अन्य एक्सप्रेस गाड़ीन की तुलना में ए गाड़ीन में लागत कम बा और आमतौर प 5-6 घंटा की भीतर अदमी अपनी गंतव्य ले पहुंच जाला। आमतौर प इ एके दिन में एक दौर क यात्रा पूरा क ले ली स, राती में अपनी मूल स्थान जहाँ से चले ली स ओइजा लौट आवेली स और एमें केवल एक रेक का प्रयोग कइल जाला। आम तौर प, एमें केवल बैठक और एगो पेंट्री कार होला, इंटरसिटी एक्सप्रेस रेलगाड़ी रोजाना चलेली स, सिवाय की: [[जबलपुर]] - हबीबगंज इंटरसिटी एक्सप्रेस दैनिक राजगढ़ इंटरसिटी एक्सप्रेस, राजगीर → दानापुर इंटरसिटी इंदौर - जबलपुर इंटरसिटी एक्सप्रेस, सप्ताह में तीन दिन भुवनेश्वर-विशाखापटनम इंटरसिटी एक्सप्रेस, दैनिक भोपाल - ग्वालियर इंटरसिटी एक्सप्रेस, सप्ताह में पांच दिन इंदौर - हज़रत निजामुद्दीन इंटरसिटी एक्सप्रेस, जेके 550 किलोमीटर (340 मील) मार्ग के कवर कइले में 11 घंटा लागेला। इ गाड़ी दुगो रैक क इस्तेमाल करेले और एमें अनारक्षित, स्लीपर और 3एसी कोच भी होला। बेंगलुरु-एर्नाकुलम इंटरसिटी एक्सप्रेस में भी 2 रैक क उपयोग होला। भुवनेश्वर - राउरकेला इंटरसिटी एक्सप्रेस, दैनिक कोयम्बटूर - मंगलूरु इंटरसिटी एक्सप्रेस में 2 रैक उपयोग होला और एके एर्नाद एक्सप्रेस से साझा कइल जाला। हल्दीबारी-कोलकाता इंटरसिटी एक्सप्रेस त्रि-साप्ताहिक 11 घंटा 10 मिनट में 630 किलोमीटर क सफ़र तय करेले। एमें चेयर कार और एसी चेयर कार कोच हो न स। इ गाड़ी सुपरफास्ट श्रेणी क ह। कटिहार-पटना इंटरसिटी रविवार को छोड़के बाकी दिन चलेले ==इंटरसिटी-एक्सप्रेस गाड़ी अलग-अलग नांव से:== * तिरुवनंतपुरम-गुरुवायूर एक्सप्रेस * ताज एक्सप्रेस * कोवाई एक्सप्रेस * ब्रिंडन एक्सप्रेस * लाल बाग एक्सप्रेस * पिनकिनी एक्सप्रेस * सतावहाना एक्सप्रेस * जन्मभूमि एक्सप्रेस * पल्लवन एक्सप्रेस * विंध्याचल एक्सप्रेस * टिपू एक्सप्रेस * चामुंडी एक्सप्रेस * प्रगति एक्सप्रेस * डेक्कन रानी एक्सप्रेस * डेक्कन एक्सप्रेस * वनाद एक्सप्रेस * वनचिनाद एक्सप्रेस * वैगई एक्सप्रेस * अमृता एक्सप्रेस * शताब्दी एक्सप्रेस * जन शताब्दी एक्सप्रेस * एर्नाकुलम / अलापी * हुतात्मा एक्सप्रेस ==मार्ग== इंटरसिटी एक्सप्रेस मार्ग (जबले बतावल न जा टेबल राउंड ट्रिप मनाई): * नई दिल्ली-कानपुर * आगरा कैंट नई दिल्ली इंटरसिटी एक्सप्रेस * नई दिल्ली-चंडीगढ़ (एक तरफ) * देहरादून-नई दिल्ली (एक तरफ) * नई दिल्ली-ग्वालियर * मुंबई पुणे इंटरसिटी एक्सप्रेस * [[मुंबई]]-नागपुर * अकोला - हैदराबाद कचेगुडा इंटरसिटी एक्सप्रेस * नागपुर अजनी - अमरावती - नागपुर अजनी इंटरसिटी एक्सप्रेस * जयपुर-[[जोधपुर]] इंटरसिटी एक्सप्रेस * भोपाल - इंदौर इंटरसिटी एक्सप्रेस * भोपाल-ग्वालियर इंटरसिटी एक्सप्रेस * भोपाल - जबलपुर इंटरसिटी एक्सप्रेस * बांद्रा सूरत इंटरसिटी एक्सप्रेस * सूरत जामनगर इंटरसिटी एक्सप्रेस * लखनऊ-[[गोरखपुर]] * भोपाल-दाहोद * भोपाल-जयपुर एक्सप्रेस * भुवनेश्वर-विशाखापत्तनम इंटरसिटी एक्सप्रेस * इंदौर-ग्वालियर इंटरसिटी एक्सप्रेस * इंदौर-भिंड एक्सप्रेस * इंदौर - हज़रत निजामुद्दीन इंटरसिटी एक्सप्रेस * इंदौर-जोधपुर रणथंभौर एक्सप्रेस * इंदौर-उदयपुर वीर भूमि एक्सप्रेस * ग्वालियर-[[उदयपुर]] * जबलपुर-रीवा इंटरसिटी एक्सप्रेस * रतलाम-अजमेर * बैंगलोर-हुबली सिद्दगंगा इंटरसिटी एक्सप्रेस * भुवनेश्वर-राउरकेला इंटरसिटी एक्सप्रेस * [[चेन्नई]] कोयम्बटूर इंटरसिटी एक्सप्रेस * बैंगलोर-शिमोगा टाउन इंटरसिटी एक्सप्रेस * एरनाकुलम जंक्शन-तिरुवनंतपुरम वनचिनाद एक्सप्रेस * [[तिरुवनंतपुरम]]-गुरुवायूर इंटरसिटी एक्सप्रेस * तिरुचिरापल्ली-तिरुनेलवेली इंटरसिटी एक्सप्रेस * [[पटना]]-डेहरी ऑन सोन इंटरसिटी एक्सप्रेस * [[गुवाहाटी]]-लेडो इंटरसिटी एक्सप्रेस * रांची-डेहरी ऑन सोन इंटरसिटी एक्सप्रेस * एरनाकुलम जंक्शन - कन्नूर मेन इंटरसिटी एक्सप्रेस * कोयम्बटूर जेएन - मैंगलोर सेंट्रल इंटरसिटी एक्सप्रेस * कटिहार-सिलीगुड़ी इंटरसिटी एक्सप्रेस * कामाख्या - डिब्रूगढ़ इंटरसिटी एक्सप्रेस * सिलीगुड़ी-धुबरी इंटरसिटी एक्सप्रेस * हावड़ा मालदा टाउन इंटरसिटी एक्सप्रेस * अलीपुरद्वार-लुमडिंग इंटरसिटी एक्सप्रेस * न्यू जलपाईगुड़ी-बालूरघाट इंटरसिटी एक्सप्रेस * पटना-धनबाद इंटरसिटी एक्सप्रेस * साहिबगंज-दानापुर इंटरसिटी एक्सप्रेस * [[वाराणसी]] लखनऊ इंटरसिटी एक्सप्रेस<ref>https://erail.in/14219-bsb-lko-exp</ref> * पुरी-संबलपुर इंटरसिटी एक्सप्रेस * अलीपुरद्वार-न्यू जलपाईगुड़ी इंटरसिटी एक्सप्रेस * कामाख्या-डकारगांव इंटरसिटी एक्सप्रेस * जोधपुर-इंदौर इंटरसिटी एक्सप्रेस<ref>{{cite web |url=https://www.cleartrip.com/trains/12466 |title=Intercity Express 12466 Time Table |publisher=cleartrip.com |date= |accessdate=18 May 2017 |archive-date=20 July 2016 |archive-url=https://web.archive.org/web/20160720113343/http://www.cleartrip.com/trains/12466 |url-status=dead }}</ref> * जयनगर-दानापुर इंटरसिटी एक्सप्रेस * [[लखनऊ]]-आगरा इंटरसिटी एक्सप्रेस * भबुआ-आरा-पटना इंटरसिटी एक्सप्रेस * कानपुर- प्रतापगढ़ इंटरसिटी एक्सप्रेस<ref>http://indiarailinfo.com/train/kanpur-pratapgarh-intercity-express-14124-cnb-to-lko/1268/452/336</ref> * वलसाड-दाहोद इंटरसिटी एक्सप्रेस * [[पुणे]] सोलापुर इंटरसिटी एक्सप्रेस ==संदर्भ== [[श्रेणी:भारतीय रेल के गाड़ी]] hdfrllcrw7xhkckj8eeb2ucd33gk51j टेम्पलेट:Wikidata 10 46668 803642 467173 2017-09-16T18:52:24Z en>JohnBlackburne 0 Nominated for merging with [[Template:Any qp]]; see [[Wikipedia:Templates for discussion/Log/2017 September 16#Template:Any qp]]. ([[WP:TW|TW]]) 803642 wikitext text/x-wiki {{Tfm/dated|page=Wikidata|otherpage=Any qp|link=Wikipedia:Templates for discussion/Log/2017 September 16#Template:Wikidata|type=tiny|help=off|bigbox={{#invoke:Noinclude|noinclude|text=yes}}}} <includeonly>{{safesubst:#invoke:Wd|main}}</includeonly><noinclude> {{Documentation}} </noinclude> qxdmvsfwl5gviv5d8h5rjnrmovy0u1m 803643 803642 2017-09-16T18:54:08Z en>Mike Peel 0 Wrap Tfm in noinclude to prevent it from messing up articles 803643 wikitext text/x-wiki <noinclude>{{Tfm/dated|page=Wikidata|otherpage=Any qp|link=Wikipedia:Templates for discussion/Log/2017 September 16#Template:Wikidata|type=tiny|help=off|bigbox={{#invoke:Noinclude|noinclude|text=yes}}}}</noinclude> <includeonly>{{safesubst:#invoke:Wd|main}}</includeonly><noinclude> {{Documentation}} </noinclude> olsy61s9wu0jagycthesow17aa07mez 803644 803643 2017-09-16T21:15:41Z en>Was a bee 0 remove carriage return which generate errors 803644 wikitext text/x-wiki <noinclude>{{Tfm/dated|page=Wikidata|otherpage=Any qp|link=Wikipedia:Templates for discussion/Log/2017 September 16#Template:Wikidata|type=tiny|help=off|bigbox={{#invoke:Noinclude|noinclude|text=yes}}}}</noinclude><includeonly>{{safesubst:#invoke:Wd|main}}</includeonly><noinclude> {{Documentation}} </noinclude> 6806pqmtwnpqqjbz0gbwpepto0jvd1o 803645 803644 2017-09-30T12:38:55Z en>Godric on Leave 0 Updating TFD template: discussion was relisted ([[WP:XFDC|XFDcloser]]) 803645 wikitext text/x-wiki <noinclude>{{Tfm/dated|page=Wikidata|otherpage=Any qp|link=Wikipedia:Templates for discussion/Log/2017 September 30#Template:Wikidata|type=tiny|help=off|bigbox={{#invoke:Noinclude|noinclude|text=yes}}}}</noinclude><includeonly>{{safesubst:#invoke:Wd|main}}</includeonly><noinclude> {{Documentation}} </noinclude> pn02jo2dfc993jrpuksslmfv7lsp0gy 803646 803645 2017-10-09T16:54:32Z en>MusikAnimal 0 Protected "[[Template:Wikidata]]": [[WP:High-risk templates|Highly visible template]]; thousands of transclusions ([Edit=Require autoconfirmed or confirmed access] (indefinite)) 803645 wikitext text/x-wiki <noinclude>{{Tfm/dated|page=Wikidata|otherpage=Any qp|link=Wikipedia:Templates for discussion/Log/2017 September 30#Template:Wikidata|type=tiny|help=off|bigbox={{#invoke:Noinclude|noinclude|text=yes}}}}</noinclude><includeonly>{{safesubst:#invoke:Wd|main}}</includeonly><noinclude> {{Documentation}} </noinclude> pn02jo2dfc993jrpuksslmfv7lsp0gy 803647 803646 2017-10-10T02:15:36Z en>Primefac 0 TFD closed with no action taken to this template 803647 wikitext text/x-wiki <includeonly>{{safesubst:#invoke:Wd|main}}</includeonly><noinclude> {{Documentation}} </noinclude> aqnmh4azo4jle51xny4knl3b1hl9873 803648 803647 2018-02-23T16:31:07Z en>Primefac 0 Changed protection level for "[[Template:Wikidata]]": [[WP:HIGHRISK|high-risk]] template with 4000+ transclusions ([Edit=Require template editor access] (indefinite) [Move=Require template editor access] (indefinite)) 803647 wikitext text/x-wiki <includeonly>{{safesubst:#invoke:Wd|main}}</includeonly><noinclude> {{Documentation}} </noinclude> aqnmh4azo4jle51xny4knl3b1hl9873 803649 803648 2018-03-05T12:34:02Z en>Primefac 0 Changed protection level for "[[Template:Wikidata]]": to match module ([Edit=Require extended confirmed access] (indefinite) [Move=Require extended confirmed access] (indefinite)) 803647 wikitext text/x-wiki <includeonly>{{safesubst:#invoke:Wd|main}}</includeonly><noinclude> {{Documentation}} </noinclude> aqnmh4azo4jle51xny4knl3b1hl9873 803650 803649 2019-07-03T10:25:41Z en>Ymblanter 0 Changed protection level for "[[Template:Wikidata]]": [[WP:High-risk templates|Highly visible template]]: request at [[WP:RFPP]] ([Edit=Require template editor access] (indefinite) [Move=Require template editor access] (indefinite)) 803647 wikitext text/x-wiki <includeonly>{{safesubst:#invoke:Wd|main}}</includeonly><noinclude> {{Documentation}} </noinclude> aqnmh4azo4jle51xny4knl3b1hl9873 803651 803650 2026-07-30T00:21:31Z SM7 3953 9 revisions imported from [[:en:Template:Wikidata]] 803647 wikitext text/x-wiki <includeonly>{{safesubst:#invoke:Wd|main}}</includeonly><noinclude> {{Documentation}} </noinclude> aqnmh4azo4jle51xny4knl3b1hl9873 Module:Wd 828 46671 803508 661806 2019-11-09T11:10:16Z en>Thayts 0 Minor improvements 803508 Scribunto text/plain -- Original module located at [[:en:Module:Wd]], [[:en:Module:Wd/i18n]] and [[:en:Module:Wd/aliasesP]]. local p = {} local arg = ... local i18n function loadSubmodules(frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end i18n = i18n or require(title .. "/i18n") p.aliasesP = p.aliasesP or mw.loadData(title .. "/aliasesP") end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end function replaceAlias(id) if p.aliasesP[id] then id = p.aliasesP[id] end return id end function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end function throwError(errorMessage, param) error(errorText(errorMessage, param)) end function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for i = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for i, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local title = nil local prefix= "" local lang if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.getEntity(id) then label = id if id:sub(1,1) == "P" then prefix = "Property:" end end prefix = "d:" .. prefix title = label -- may be nil else -- try short name first if requested if short then label = p._property{p.aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label, lang = mw.wikibase.getLabelWithLang(id) -- don't allow language fallback if lang ~= self.langCode then label = nil end end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.sitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead title = id prefix = "d:Property:" end end if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then return mw.getCurrentFrame():extensionTag("math", datavalue) elseif subtype == 'external-id' and link then local url = p._property{p.aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang then return datavalue['text'], datavalue['language'] elseif datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getEntity(globe):getLabel("en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://tools.wmflabs.org/geohack/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for i, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end local now = os.date('!*t') startTime = self:getSingleRawQualifier(claim, p.aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, p.aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) elseif endTime == " " then -- end time is 'unknown', assume it is somewhere in the past; -- we can do this by taking the current date as a placeholder for the end time endTimeY = now['year'] endTimeM = now['month'] endTimeD = now['day'] end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(now['year'], now['month'], now['day'], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(now['year'], now['month'], now['day'], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(now['year'], now['month'], now['day'], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(now['year'], now['month'], now['day'], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag:sub(-1) ~= 's' then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for i, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self.conf:getValue(claim.mainsnak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local version = 1 -- increase this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[p.aliasesP.importedFrom] then statement.snaks[p.aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[p.aliasesP.inferredFrom] then statement.snaks[p.aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[p.aliasesP.typeOfReference] then statement.snaks[p.aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[p.aliasesP.image] then statement.snaks[p.aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, p.aliasesP.language) == self.conf.langName then statement.snaks[p.aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == p.aliasesP.author then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == p.aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- if there are multiple parameter values (authors), add those too for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.sitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.sitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][p.aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][p.aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][p.aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][p.aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, p.aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) else, do some default rendering of name-value pairs, but only if at least "stated in", "reference URL" or "title" is present elseif params[p.aliasesP.statedIn] or params[p.aliasesP.referenceURL] or params[p.aliasesP.title] then citeParams['default'] = {} -- start by adding authors up front if params[p.aliasesP.author] and #params[p.aliasesP.author] > 0 then citeParams['default'][#citeParams['default'] + 1] = table.concat(params[p.aliasesP.author], " & ") end -- combine "reference URL" and "title" into one link if both are present if params[p.aliasesP.referenceURL] and params[p.aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '[' .. params[p.aliasesP.referenceURL][1] .. ' "' .. params[p.aliasesP.title][1] .. '"]' elseif params[p.aliasesP.referenceURL] then citeParams['default'][#citeParams['default'] + 1] = params[p.aliasesP.referenceURL][1] elseif params[p.aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '"' .. params[p.aliasesP.title][1] .. '"' end -- then add "stated in" if params[p.aliasesP.statedIn] then citeParams['default'][#citeParams['default'] + 1] = "''" .. params[p.aliasesP.statedIn][1] .. "''" end -- remove previously added parameters so that they won't be added a second time params[p.aliasesP.author] = nil params[p.aliasesP.referenceURL] = nil params[p.aliasesP.title] = nil params[p.aliasesP.statedIn] = nil -- add the rest of the parameters for i, v in pairs(params) do i = self.conf:getLabel(i) if i ~= "" then citeParams['default'][#citeParams['default'] + 1] = i .. ": " .. v[1] end end value = table.concat(citeParams['default'], "; ") if value ~= "" then value = value .. "." end end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for i, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for i, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.sitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for i, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for i2, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end function getEntityId(arg, allowOmitPropPrefix) if not arg then return nil elseif arg:sub(1,1):upper() == "Q" then return arg:upper() -- entity ID of an item was given elseif arg:sub(1,9):lower() == "property:" then return replaceAlias(mw.text.trim(arg:sub(10))):upper() -- entity ID of a property was given elseif allowOmitPropPrefix and arg ~= "" then return replaceAlias(arg):upper() -- could be an entity ID of a property without "Property:" prefix else return "" end end function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end function claimCommand(args, funcName) local _ = Config:new() _:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- process flags and commands repeat lastArg = nextArg(args) until not _:processFlagOrCommand(lastArg) -- use the entity ID from the positional arguments if given _.entityID = getEntityId(lastArg, false) if not _.entityID or _.entityID == "" then -- if no positional ID is given, use the eid argument if given _.entityID = getEntityId(args[p.args.eid], true) if _.entityID == "" then -- if eid was explicitly set to empty, then this returns an empty string return "" elseif not _.entityID then -- by default, use the item-entity connected to the current page _.entityID = mw.wikibase.getEntityIdForCurrentPage() end else lastArg = nextArg(args) end _.entity = mw.wikibase.getEntity(_.entityID) _.propertyID = replaceAlias(lastArg) if not _.entity or not _.propertyID then return "" -- we cannot continue without an entity or a property ID end _.propertyID = _.propertyID:upper() if not _.entity.claims or not _.entity.claims[_.propertyID] then return "" -- there is no use to continue without any claims end claims = _.entity.claims[_.propertyID] if _.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > _.states.qualifiersCount then -- claim ID or literal value has been given _.propertyValue = nextArg(args) end for i = 1, _.states.qualifiersCount do -- check if given qualifier ID is an alias and add it _.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif _.states[parameters.reference] then -- do further processing if "reference(s)" command was given _.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if _.propertyValue then _.propertyValue = replaceSpecialChars(_.propertyValue) if _.propertyValue ~= "" and mw.text.trim(_.propertyValue) == "" then _.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else _.propertyValue = mw.text.trim(_.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif _.states.qualifiersCount > 0 then -- "qualifier(s)" command given if _.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif _.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if _.states.qualifiersCount > 0 and not _.states[parameters.property] then _.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if _.states[parameters.reference] and not _.states[parameters.property] and _.states.qualifiersCount == 0 and not _.states[parameters.reference].rawValue then _.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if _.states.qualifiersCount == 1 then _.separators["sep"..parameters.qualifier] = _.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators _:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(_.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and _.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not _.states[parameters.property] then _.states[parameters.property] = State:new(_) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if _.singleClaim then _.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if _.sourcedOnly and not _.states[parameters.reference] then _:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states _:setFormatAndSeparators(_.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to _.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or p.aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end _.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = _:concatValues(_.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if _.editable and value ~= "" then value = value .. _:getEditIcon() end return value end function generalCommand(args, funcName) local _ = Config:new() _.curState = State:new(_) local lastArg local value = nil repeat lastArg = nextArg(args) until not _:processFlag(lastArg) -- use the entity ID from the positional arguments if given _.entityID = getEntityId(lastArg, true) if not _.entityID or _.entityID == "" then -- if no positional ID is given, use the eid argument if given _.entityID = getEntityId(args[p.args.eid], true) if _.entityID == "" then -- if eid was explicitly set to empty, then this returns an empty string return "" elseif not _.entityID then -- by default, use the item-entity connected to the current page _.entityID = mw.wikibase.getEntityIdForCurrentPage() end end _.entity = mw.wikibase.getEntity(_.entityID) if not _.entity then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = _:getLabel(_.entityID, _.curState.rawValue, _.curState.linked, _.curState.shortName) elseif funcName == p.generalCommands.title then _.inSitelinks = true if _.entityID:sub(1,1) == "Q" then value = mw.wikibase.sitelink(_.entityID) end if _.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then if _.entity.descriptions[_.langCode] then value = _.entity.descriptions[_.langCode].value end else local parsedFormat, formatParams local hooks = {count = 0} if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then _.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not _.entity.aliases or not _.entity.aliases[_.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = _.entity.aliases[_.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators _:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) _:setFormatAndSeparators(_.curState, parsedFormat) -- iterate to collect values value = _:concatValues(_.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not _.entity.sitelinks or not _.entity.sitelinks[_.siteID] or not _.entity.sitelinks[_.siteID].badges then return "" -- there is no use to continue without any badges end local badges = _.entity.sitelinks[_.siteID].badges _.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators _:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) _:setFormatAndSeparators(_.curState, parsedFormat) -- iterate to collect values value = _:concatValues(_.curState:iterate(badges, hooks)) end end value = value or "" if _.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. _:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) function establishCommands(commandList, commandFunc) for commandIndex, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadSubmodules(frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadSubmodules() return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) local f, args, i, v loadSubmodules(frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p j103xrm4tflt2pu79hh2ftk2ciash91 803509 803508 2019-11-09T15:29:58Z en>Thayts 0 Added new named argument 'date' 803509 Scribunto text/plain -- Original module located at [[:en:Module:Wd]], [[:en:Module:Wd/i18n]] and [[:en:Module:Wd/aliasesP]]. local p = {} local arg = ... local i18n function loadSubmodules(frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end i18n = i18n or require(title .. "/i18n") p.aliasesP = p.aliasesP or mw.loadData(title .. "/aliasesP") end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", date = "date" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end function replaceAlias(id) if p.aliasesP[id] then id = p.aliasesP[id] end return id end function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end function throwError(errorMessage, param) error(errorText(errorMessage, param)) end function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for i = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for i, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local title = nil local prefix= "" local lang if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.getEntity(id) then label = id if id:sub(1,1) == "P" then prefix = "Property:" end end prefix = "d:" .. prefix title = label -- may be nil else -- try short name first if requested if short then label = p._property{p.aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label, lang = mw.wikibase.getLabelWithLang(id) -- don't allow language fallback if lang ~= self.langCode then label = nil end end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.sitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead title = id prefix = "d:Property:" end end if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then return mw.getCurrentFrame():extensionTag("math", datavalue) elseif subtype == 'external-id' and link then local url = p._property{p.aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang then return datavalue['text'], datavalue['language'] elseif datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getEntity(globe):getLabel("en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://tools.wmflabs.org/geohack/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for i, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, p.aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, p.aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag:sub(-1) ~= 's' then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for i, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self.conf:getValue(claim.mainsnak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local version = 1 -- increase this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[p.aliasesP.importedFrom] then statement.snaks[p.aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[p.aliasesP.inferredFrom] then statement.snaks[p.aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[p.aliasesP.typeOfReference] then statement.snaks[p.aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[p.aliasesP.image] then statement.snaks[p.aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, p.aliasesP.language) == self.conf.langName then statement.snaks[p.aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == p.aliasesP.author then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == p.aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- if there are multiple parameter values (authors), add those too for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.sitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.sitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][p.aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][p.aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][p.aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][p.aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, p.aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) else, do some default rendering of name-value pairs, but only if at least "stated in", "reference URL" or "title" is present elseif params[p.aliasesP.statedIn] or params[p.aliasesP.referenceURL] or params[p.aliasesP.title] then citeParams['default'] = {} -- start by adding authors up front if params[p.aliasesP.author] and #params[p.aliasesP.author] > 0 then citeParams['default'][#citeParams['default'] + 1] = table.concat(params[p.aliasesP.author], " & ") end -- combine "reference URL" and "title" into one link if both are present if params[p.aliasesP.referenceURL] and params[p.aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '[' .. params[p.aliasesP.referenceURL][1] .. ' "' .. params[p.aliasesP.title][1] .. '"]' elseif params[p.aliasesP.referenceURL] then citeParams['default'][#citeParams['default'] + 1] = params[p.aliasesP.referenceURL][1] elseif params[p.aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '"' .. params[p.aliasesP.title][1] .. '"' end -- then add "stated in" if params[p.aliasesP.statedIn] then citeParams['default'][#citeParams['default'] + 1] = "''" .. params[p.aliasesP.statedIn][1] .. "''" end -- remove previously added parameters so that they won't be added a second time params[p.aliasesP.author] = nil params[p.aliasesP.referenceURL] = nil params[p.aliasesP.title] = nil params[p.aliasesP.statedIn] = nil -- add the rest of the parameters for i, v in pairs(params) do i = self.conf:getLabel(i) if i ~= "" then citeParams['default'][#citeParams['default'] + 1] = i .. ": " .. v[1] end end value = table.concat(citeParams['default'], "; ") if value ~= "" then value = value .. "." end end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for i, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for i, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.sitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for i, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for i2, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end function getEntityId(arg, allowOmitPropPrefix) if not arg then return nil elseif arg:sub(1,1):upper() == "Q" then return arg:upper() -- entity ID of an item was given elseif arg:sub(1,9):lower() == "property:" then return replaceAlias(mw.text.trim(arg:sub(10))):upper() -- entity ID of a property was given elseif allowOmitPropPrefix and arg ~= "" then return replaceAlias(arg):upper() -- could be an entity ID of a property without "Property:" prefix else return "" end end function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end function claimCommand(args, funcName) local _ = Config:new() _:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then _.atDate = {parseDate(args[p.args.date])} _.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not _:processFlagOrCommand(lastArg) -- use the entity ID from the positional arguments if given _.entityID = getEntityId(lastArg, false) if not _.entityID or _.entityID == "" then -- if no positional ID is given, use the eid argument if given _.entityID = getEntityId(args[p.args.eid], true) if _.entityID == "" then -- if eid was explicitly set to empty, then this returns an empty string return "" elseif not _.entityID then -- by default, use the item-entity connected to the current page _.entityID = mw.wikibase.getEntityIdForCurrentPage() end else lastArg = nextArg(args) end _.entity = mw.wikibase.getEntity(_.entityID) _.propertyID = replaceAlias(lastArg) if not _.entity or not _.propertyID then return "" -- we cannot continue without an entity or a property ID end _.propertyID = _.propertyID:upper() if not _.entity.claims or not _.entity.claims[_.propertyID] then return "" -- there is no use to continue without any claims end claims = _.entity.claims[_.propertyID] if _.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > _.states.qualifiersCount then -- claim ID or literal value has been given _.propertyValue = nextArg(args) end for i = 1, _.states.qualifiersCount do -- check if given qualifier ID is an alias and add it _.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif _.states[parameters.reference] then -- do further processing if "reference(s)" command was given _.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if _.propertyValue then _.propertyValue = replaceSpecialChars(_.propertyValue) if _.propertyValue ~= "" and mw.text.trim(_.propertyValue) == "" then _.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else _.propertyValue = mw.text.trim(_.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif _.states.qualifiersCount > 0 then -- "qualifier(s)" command given if _.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif _.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if _.states.qualifiersCount > 0 and not _.states[parameters.property] then _.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if _.states[parameters.reference] and not _.states[parameters.property] and _.states.qualifiersCount == 0 and not _.states[parameters.reference].rawValue then _.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if _.states.qualifiersCount == 1 then _.separators["sep"..parameters.qualifier] = _.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators _:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(_.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and _.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not _.states[parameters.property] then _.states[parameters.property] = State:new(_) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if _.singleClaim then _.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if _.sourcedOnly and not _.states[parameters.reference] then _:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states _:setFormatAndSeparators(_.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to _.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or p.aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end _.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = _:concatValues(_.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if _.editable and value ~= "" then value = value .. _:getEditIcon() end return value end function generalCommand(args, funcName) local _ = Config:new() _.curState = State:new(_) local lastArg local value = nil repeat lastArg = nextArg(args) until not _:processFlag(lastArg) -- use the entity ID from the positional arguments if given _.entityID = getEntityId(lastArg, true) if not _.entityID or _.entityID == "" then -- if no positional ID is given, use the eid argument if given _.entityID = getEntityId(args[p.args.eid], true) if _.entityID == "" then -- if eid was explicitly set to empty, then this returns an empty string return "" elseif not _.entityID then -- by default, use the item-entity connected to the current page _.entityID = mw.wikibase.getEntityIdForCurrentPage() end end _.entity = mw.wikibase.getEntity(_.entityID) if not _.entity then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = _:getLabel(_.entityID, _.curState.rawValue, _.curState.linked, _.curState.shortName) elseif funcName == p.generalCommands.title then _.inSitelinks = true if _.entityID:sub(1,1) == "Q" then value = mw.wikibase.sitelink(_.entityID) end if _.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then if _.entity.descriptions[_.langCode] then value = _.entity.descriptions[_.langCode].value end else local parsedFormat, formatParams local hooks = {count = 0} if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then _.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not _.entity.aliases or not _.entity.aliases[_.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = _.entity.aliases[_.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators _:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) _:setFormatAndSeparators(_.curState, parsedFormat) -- iterate to collect values value = _:concatValues(_.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not _.entity.sitelinks or not _.entity.sitelinks[_.siteID] or not _.entity.sitelinks[_.siteID].badges then return "" -- there is no use to continue without any badges end local badges = _.entity.sitelinks[_.siteID].badges _.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators _:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) _:setFormatAndSeparators(_.curState, parsedFormat) -- iterate to collect values value = _:concatValues(_.curState:iterate(badges, hooks)) end end value = value or "" if _.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. _:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) function establishCommands(commandList, commandFunc) for commandIndex, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadSubmodules(frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadSubmodules() return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) local f, args, i, v loadSubmodules(frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p mv3hj1bx2thaaovyw6wd40d5fsl62aw 803510 803509 2019-11-11T20:04:39Z en>Thayts 0 Added validity check for entity ID 803510 Scribunto text/plain -- Original module located at [[:en:Module:Wd]], [[:en:Module:Wd/i18n]] and [[:en:Module:Wd/aliasesP]]. local p = {} local arg = ... local i18n function loadSubmodules(frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end i18n = i18n or require(title .. "/i18n") p.aliasesP = p.aliasesP or mw.loadData(title .. "/aliasesP") end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", date = "date" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end function replaceAlias(id) if p.aliasesP[id] then id = p.aliasesP[id] end return id end function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end function throwError(errorMessage, param) error(errorText(errorMessage, param)) end function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for i = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for i, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local title = nil local prefix= "" local lang if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.getEntity(id) then label = id if id:sub(1,1) == "P" then prefix = "Property:" end end prefix = "d:" .. prefix title = label -- may be nil else -- try short name first if requested if short then label = p._property{p.aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label, lang = mw.wikibase.getLabelWithLang(id) -- don't allow language fallback if lang ~= self.langCode then label = nil end end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.sitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead title = id prefix = "d:Property:" end end if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then return mw.getCurrentFrame():extensionTag("math", datavalue) elseif subtype == 'external-id' and link then local url = p._property{p.aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang then return datavalue['text'], datavalue['language'] elseif datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getEntity(globe):getLabel("en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://tools.wmflabs.org/geohack/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for i, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, p.aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, p.aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag:sub(-1) ~= 's' then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for i, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self.conf:getValue(claim.mainsnak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local version = 1 -- increase this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[p.aliasesP.importedFrom] then statement.snaks[p.aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[p.aliasesP.inferredFrom] then statement.snaks[p.aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[p.aliasesP.typeOfReference] then statement.snaks[p.aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[p.aliasesP.image] then statement.snaks[p.aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, p.aliasesP.language) == self.conf.langName then statement.snaks[p.aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == p.aliasesP.author then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == p.aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- if there are multiple parameter values (authors), add those too for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.sitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.sitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][p.aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][p.aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][p.aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][p.aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, p.aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) else, do some default rendering of name-value pairs, but only if at least "stated in", "reference URL" or "title" is present elseif params[p.aliasesP.statedIn] or params[p.aliasesP.referenceURL] or params[p.aliasesP.title] then citeParams['default'] = {} -- start by adding authors up front if params[p.aliasesP.author] and #params[p.aliasesP.author] > 0 then citeParams['default'][#citeParams['default'] + 1] = table.concat(params[p.aliasesP.author], " & ") end -- combine "reference URL" and "title" into one link if both are present if params[p.aliasesP.referenceURL] and params[p.aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '[' .. params[p.aliasesP.referenceURL][1] .. ' "' .. params[p.aliasesP.title][1] .. '"]' elseif params[p.aliasesP.referenceURL] then citeParams['default'][#citeParams['default'] + 1] = params[p.aliasesP.referenceURL][1] elseif params[p.aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '"' .. params[p.aliasesP.title][1] .. '"' end -- then add "stated in" if params[p.aliasesP.statedIn] then citeParams['default'][#citeParams['default'] + 1] = "''" .. params[p.aliasesP.statedIn][1] .. "''" end -- remove previously added parameters so that they won't be added a second time params[p.aliasesP.author] = nil params[p.aliasesP.referenceURL] = nil params[p.aliasesP.title] = nil params[p.aliasesP.statedIn] = nil -- add the rest of the parameters for i, v in pairs(params) do i = self.conf:getLabel(i) if i ~= "" then citeParams['default'][#citeParams['default'] + 1] = i .. ": " .. v[1] end end value = table.concat(citeParams['default'], "; ") if value ~= "" then value = value .. "." end end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for i, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for i, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.sitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for i, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for i2, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end function getEntityId(arg, allowOmitPropPrefix) if not arg then return nil end if arg:sub(1,9):lower() == "property:" then arg = replaceAlias(mw.text.trim(arg:sub(10))) -- entity ID of a property was given if arg:sub(1,1):upper() ~= "P" then return "" end elseif allowOmitPropPrefix then arg = replaceAlias(arg) -- could be an entity ID of a property without "Property:" prefix elseif arg:sub(1,1):upper() ~= "Q" then return "" end if not mw.wikibase.isValidEntityId(arg) then return "" end return arg:upper() end function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end function claimCommand(args, funcName) local _ = Config:new() _:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then _.atDate = {parseDate(args[p.args.date])} _.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not _:processFlagOrCommand(lastArg) -- use the entity ID from the positional arguments if given _.entityID = getEntityId(lastArg, false) if not _.entityID or _.entityID == "" then -- if no positional ID is given, use the eid argument if given _.entityID = getEntityId(args[p.args.eid], true) if _.entityID == "" then -- if eid was explicitly set to empty, then this returns an empty string return "" elseif not _.entityID then -- by default, use the item-entity connected to the current page _.entityID = mw.wikibase.getEntityIdForCurrentPage() end else lastArg = nextArg(args) end _.entity = mw.wikibase.getEntity(_.entityID) _.propertyID = replaceAlias(lastArg) if not _.entity or not _.propertyID then return "" -- we cannot continue without an entity or a property ID end _.propertyID = _.propertyID:upper() if not _.entity.claims or not _.entity.claims[_.propertyID] then return "" -- there is no use to continue without any claims end claims = _.entity.claims[_.propertyID] if _.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > _.states.qualifiersCount then -- claim ID or literal value has been given _.propertyValue = nextArg(args) end for i = 1, _.states.qualifiersCount do -- check if given qualifier ID is an alias and add it _.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif _.states[parameters.reference] then -- do further processing if "reference(s)" command was given _.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if _.propertyValue then _.propertyValue = replaceSpecialChars(_.propertyValue) if _.propertyValue ~= "" and mw.text.trim(_.propertyValue) == "" then _.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else _.propertyValue = mw.text.trim(_.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif _.states.qualifiersCount > 0 then -- "qualifier(s)" command given if _.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif _.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if _.states.qualifiersCount > 0 and not _.states[parameters.property] then _.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if _.states[parameters.reference] and not _.states[parameters.property] and _.states.qualifiersCount == 0 and not _.states[parameters.reference].rawValue then _.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if _.states.qualifiersCount == 1 then _.separators["sep"..parameters.qualifier] = _.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators _:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(_.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and _.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not _.states[parameters.property] then _.states[parameters.property] = State:new(_) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if _.singleClaim then _.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if _.sourcedOnly and not _.states[parameters.reference] then _:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states _:setFormatAndSeparators(_.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to _.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or p.aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end _.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = _:concatValues(_.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if _.editable and value ~= "" then value = value .. _:getEditIcon() end return value end function generalCommand(args, funcName) local _ = Config:new() _.curState = State:new(_) local lastArg local value = nil repeat lastArg = nextArg(args) until not _:processFlag(lastArg) -- use the entity ID from the positional arguments if given _.entityID = getEntityId(lastArg, true) if not _.entityID or _.entityID == "" then -- if no positional ID is given, use the eid argument if given _.entityID = getEntityId(args[p.args.eid], true) if _.entityID == "" then -- if eid was explicitly set to empty, then this returns an empty string return "" elseif not _.entityID then -- by default, use the item-entity connected to the current page _.entityID = mw.wikibase.getEntityIdForCurrentPage() end end _.entity = mw.wikibase.getEntity(_.entityID) if not _.entity then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = _:getLabel(_.entityID, _.curState.rawValue, _.curState.linked, _.curState.shortName) elseif funcName == p.generalCommands.title then _.inSitelinks = true if _.entityID:sub(1,1) == "Q" then value = mw.wikibase.sitelink(_.entityID) end if _.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then if _.entity.descriptions[_.langCode] then value = _.entity.descriptions[_.langCode].value end else local parsedFormat, formatParams local hooks = {count = 0} if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then _.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not _.entity.aliases or not _.entity.aliases[_.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = _.entity.aliases[_.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators _:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) _:setFormatAndSeparators(_.curState, parsedFormat) -- iterate to collect values value = _:concatValues(_.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not _.entity.sitelinks or not _.entity.sitelinks[_.siteID] or not _.entity.sitelinks[_.siteID].badges then return "" -- there is no use to continue without any badges end local badges = _.entity.sitelinks[_.siteID].badges _.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators _:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) _:setFormatAndSeparators(_.curState, parsedFormat) -- iterate to collect values value = _:concatValues(_.curState:iterate(badges, hooks)) end end value = value or "" if _.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. _:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) function establishCommands(commandList, commandFunc) for commandIndex, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadSubmodules(frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadSubmodules() return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) local f, args, i, v loadSubmodules(frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p 6sbowg0r10rkc0g4n4lt3vfp0y27cyf 803511 803510 2019-11-16T16:23:22Z en>Thayts 0 Minor performance improvement 803511 Scribunto text/plain -- Original module located at [[:en:Module:Wd]], [[:en:Module:Wd/i18n]] and [[:en:Module:Wd/aliasesP]]. local p = {} local arg = ... local i18n function loadSubmodules(frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end i18n = i18n or require(title .. "/i18n") p.aliasesP = p.aliasesP or mw.loadData(title .. "/aliasesP") end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", date = "date" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end function replaceAlias(id) if p.aliasesP[id] then id = p.aliasesP[id] end return id end function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end function throwError(errorMessage, param) error(errorText(errorMessage, param)) end function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for i = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for i, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local title = nil local prefix= "" local lang if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.getEntity(id) then label = id if id:sub(1,1) == "P" then prefix = "Property:" end end prefix = "d:" .. prefix title = label -- may be nil else -- try short name first if requested if short then label = p._property{p.aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label, lang = mw.wikibase.getLabelWithLang(id) -- don't allow language fallback if lang ~= self.langCode then label = nil end end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.sitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead title = id prefix = "d:Property:" end end if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then return mw.getCurrentFrame():extensionTag("math", datavalue) elseif subtype == 'external-id' and link then local url = p._property{p.aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang then return datavalue['text'], datavalue['language'] elseif datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getEntity(globe):getLabel("en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://tools.wmflabs.org/geohack/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for i, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, p.aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, p.aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag:sub(-1) ~= 's' then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for i, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self.conf:getValue(claim.mainsnak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local version = 1 -- increase this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[p.aliasesP.importedFrom] then statement.snaks[p.aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[p.aliasesP.inferredFrom] then statement.snaks[p.aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[p.aliasesP.typeOfReference] then statement.snaks[p.aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[p.aliasesP.image] then statement.snaks[p.aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, p.aliasesP.language) == self.conf.langName then statement.snaks[p.aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == p.aliasesP.author then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == p.aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- if there are multiple parameter values (authors), add those too for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.sitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.sitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][p.aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][p.aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][p.aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][p.aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, p.aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) else, do some default rendering of name-value pairs, but only if at least "stated in", "reference URL" or "title" is present elseif params[p.aliasesP.statedIn] or params[p.aliasesP.referenceURL] or params[p.aliasesP.title] then citeParams['default'] = {} -- start by adding authors up front if params[p.aliasesP.author] and #params[p.aliasesP.author] > 0 then citeParams['default'][#citeParams['default'] + 1] = table.concat(params[p.aliasesP.author], " & ") end -- combine "reference URL" and "title" into one link if both are present if params[p.aliasesP.referenceURL] and params[p.aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '[' .. params[p.aliasesP.referenceURL][1] .. ' "' .. params[p.aliasesP.title][1] .. '"]' elseif params[p.aliasesP.referenceURL] then citeParams['default'][#citeParams['default'] + 1] = params[p.aliasesP.referenceURL][1] elseif params[p.aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '"' .. params[p.aliasesP.title][1] .. '"' end -- then add "stated in" if params[p.aliasesP.statedIn] then citeParams['default'][#citeParams['default'] + 1] = "''" .. params[p.aliasesP.statedIn][1] .. "''" end -- remove previously added parameters so that they won't be added a second time params[p.aliasesP.author] = nil params[p.aliasesP.referenceURL] = nil params[p.aliasesP.title] = nil params[p.aliasesP.statedIn] = nil -- add the rest of the parameters for i, v in pairs(params) do i = self.conf:getLabel(i) if i ~= "" then citeParams['default'][#citeParams['default'] + 1] = i .. ": " .. v[1] end end value = table.concat(citeParams['default'], "; ") if value ~= "" then value = value .. "." end end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for i, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for i, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.sitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for i, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for i2, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end function getEntityId(arg, allowOmitPropPrefix) if not arg then return nil end if arg:sub(1,9):lower() == "property:" then arg = replaceAlias(mw.text.trim(arg:sub(10))) -- entity ID of a property was given if arg:sub(1,1):upper() ~= "P" then return "" end elseif allowOmitPropPrefix then arg = replaceAlias(arg) -- could be an entity ID of a property without "Property:" prefix elseif arg:sub(1,1):upper() ~= "Q" then return "" end if not mw.wikibase.isValidEntityId(arg) then return "" end return arg:upper() end function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end function claimCommand(args, funcName) local _ = Config:new() _:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then _.atDate = {parseDate(args[p.args.date])} _.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not _:processFlagOrCommand(lastArg) -- use the entity ID from the positional arguments if given _.entityID = getEntityId(lastArg, false) if not _.entityID or _.entityID == "" then -- if no positional ID is given, use the eid argument if given _.entityID = getEntityId(args[p.args.eid], true) if _.entityID == "" then -- if eid was explicitly set to empty, then this returns an empty string return "" elseif not _.entityID then -- by default, use the item-entity connected to the current page _.entityID = mw.wikibase.getEntityIdForCurrentPage() end else lastArg = nextArg(args) end _.entity = mw.wikibase.getEntity(_.entityID) _.propertyID = replaceAlias(lastArg) if not _.entity or not _.propertyID then return "" -- we cannot continue without an entity or a property ID end _.propertyID = _.propertyID:upper() if not _.entity.claims or not _.entity.claims[_.propertyID] then return "" -- there is no use to continue without any claims end claims = _.entity.claims[_.propertyID] if _.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > _.states.qualifiersCount then -- claim ID or literal value has been given _.propertyValue = nextArg(args) end for i = 1, _.states.qualifiersCount do -- check if given qualifier ID is an alias and add it _.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif _.states[parameters.reference] then -- do further processing if "reference(s)" command was given _.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if _.propertyValue then _.propertyValue = replaceSpecialChars(_.propertyValue) if _.propertyValue ~= "" and mw.text.trim(_.propertyValue) == "" then _.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else _.propertyValue = mw.text.trim(_.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif _.states.qualifiersCount > 0 then -- "qualifier(s)" command given if _.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif _.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if _.states.qualifiersCount > 0 and not _.states[parameters.property] then _.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if _.states[parameters.reference] and not _.states[parameters.property] and _.states.qualifiersCount == 0 and not _.states[parameters.reference].rawValue then _.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if _.states.qualifiersCount == 1 then _.separators["sep"..parameters.qualifier] = _.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators _:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(_.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and _.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not _.states[parameters.property] then _.states[parameters.property] = State:new(_) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if _.singleClaim then _.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if _.sourcedOnly and not _.states[parameters.reference] then _:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states _:setFormatAndSeparators(_.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to _.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or p.aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end _.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = _:concatValues(_.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if _.editable and value ~= "" then value = value .. _:getEditIcon() end return value end function generalCommand(args, funcName) local _ = Config:new() _.curState = State:new(_) local lastArg local value = nil repeat lastArg = nextArg(args) until not _:processFlag(lastArg) -- use the entity ID from the positional arguments if given _.entityID = getEntityId(lastArg, true) if not _.entityID or _.entityID == "" then -- if no positional ID is given, use the eid argument if given _.entityID = getEntityId(args[p.args.eid], true) if _.entityID == "" then -- if eid was explicitly set to empty, then this returns an empty string return "" elseif not _.entityID then -- by default, use the item-entity connected to the current page _.entityID = mw.wikibase.getEntityIdForCurrentPage() end end if not _.entityID then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = _:getLabel(_.entityID, _.curState.rawValue, _.curState.linked, _.curState.shortName) elseif funcName == p.generalCommands.title then _.inSitelinks = true if _.entityID:sub(1,1) == "Q" then value = mw.wikibase.sitelink(_.entityID) end if _.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then _.entity = mw.wikibase.getEntity(_.entityID) if _.entity.descriptions[_.langCode] then value = _.entity.descriptions[_.langCode].value end else local parsedFormat, formatParams local hooks = {count = 0} _.entity = mw.wikibase.getEntity(_.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then _.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not _.entity.aliases or not _.entity.aliases[_.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = _.entity.aliases[_.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators _:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) _:setFormatAndSeparators(_.curState, parsedFormat) -- iterate to collect values value = _:concatValues(_.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not _.entity.sitelinks or not _.entity.sitelinks[_.siteID] or not _.entity.sitelinks[_.siteID].badges then return "" -- there is no use to continue without any badges end local badges = _.entity.sitelinks[_.siteID].badges _.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators _:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) _:setFormatAndSeparators(_.curState, parsedFormat) -- iterate to collect values value = _:concatValues(_.curState:iterate(badges, hooks)) end end value = value or "" if _.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. _:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) function establishCommands(commandList, commandFunc) for commandIndex, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadSubmodules(frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadSubmodules() return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) local f, args, i, v loadSubmodules(frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p rnz4vmitjjn00oi2n5ze633f7jmzr4w 803512 803511 2019-11-17T11:01:52Z en>Thayts 0 Minor performance improvements 803512 Scribunto text/plain -- Original module located at [[:en:Module:Wd]], [[:en:Module:Wd/i18n]] and [[:en:Module:Wd/aliasesP]]. local p = {} local arg = ... local i18n function loadSubmodules(frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end i18n = i18n or require(title .. "/i18n") p.aliasesP = p.aliasesP or mw.loadData(title .. "/aliasesP") end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", date = "date" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end function replaceAlias(id) if p.aliasesP[id] then id = p.aliasesP[id] end return id end function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end function throwError(errorMessage, param) error(errorText(errorMessage, param)) end function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for i = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for i, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local title = nil local prefix= "" if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) then label = id if id:sub(1,1) == "P" then prefix = "Property:" end end prefix = "d:" .. prefix title = label -- may be nil else -- try short name first if requested if short then label = p._property{p.aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.sitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead title = id prefix = "d:Property:" end end if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then return mw.getCurrentFrame():extensionTag("math", datavalue) elseif subtype == 'external-id' and link then local url = p._property{p.aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang then return datavalue['text'], datavalue['language'] elseif datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://tools.wmflabs.org/geohack/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for i, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, p.aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, p.aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag:sub(-1) ~= 's' then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for i, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self.conf:getValue(claim.mainsnak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local version = 1 -- increase this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[p.aliasesP.importedFrom] then statement.snaks[p.aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[p.aliasesP.inferredFrom] then statement.snaks[p.aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[p.aliasesP.typeOfReference] then statement.snaks[p.aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[p.aliasesP.image] then statement.snaks[p.aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, p.aliasesP.language) == self.conf.langName then statement.snaks[p.aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == p.aliasesP.author then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == p.aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- if there are multiple parameter values (authors), add those too for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.sitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.sitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][p.aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][p.aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][p.aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][p.aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, p.aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) else, do some default rendering of name-value pairs, but only if at least "stated in", "reference URL" or "title" is present elseif params[p.aliasesP.statedIn] or params[p.aliasesP.referenceURL] or params[p.aliasesP.title] then citeParams['default'] = {} -- start by adding authors up front if params[p.aliasesP.author] and #params[p.aliasesP.author] > 0 then citeParams['default'][#citeParams['default'] + 1] = table.concat(params[p.aliasesP.author], " & ") end -- combine "reference URL" and "title" into one link if both are present if params[p.aliasesP.referenceURL] and params[p.aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '[' .. params[p.aliasesP.referenceURL][1] .. ' "' .. params[p.aliasesP.title][1] .. '"]' elseif params[p.aliasesP.referenceURL] then citeParams['default'][#citeParams['default'] + 1] = params[p.aliasesP.referenceURL][1] elseif params[p.aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '"' .. params[p.aliasesP.title][1] .. '"' end -- then add "stated in" if params[p.aliasesP.statedIn] then citeParams['default'][#citeParams['default'] + 1] = "''" .. params[p.aliasesP.statedIn][1] .. "''" end -- remove previously added parameters so that they won't be added a second time params[p.aliasesP.author] = nil params[p.aliasesP.referenceURL] = nil params[p.aliasesP.title] = nil params[p.aliasesP.statedIn] = nil -- add the rest of the parameters for i, v in pairs(params) do i = self.conf:getLabel(i) if i ~= "" then citeParams['default'][#citeParams['default'] + 1] = i .. ": " .. v[1] end end value = table.concat(citeParams['default'], "; ") if value ~= "" then value = value .. "." end end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for i, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for i, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.sitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for i, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for i2, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end function getEntityId(arg, allowOmitPropPrefix) if not arg then return nil end if arg:sub(1,9):lower() == "property:" then arg = replaceAlias(mw.text.trim(arg:sub(10))) -- entity ID of a property was given if arg:sub(1,1):upper() ~= "P" then return "" end elseif allowOmitPropPrefix then arg = replaceAlias(arg) -- could be an entity ID of a property without "Property:" prefix elseif arg:sub(1,1):upper() ~= "Q" then return "" end if not mw.wikibase.isValidEntityId(arg) then return "" end return arg:upper() end function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end function claimCommand(args, funcName) local _ = Config:new() _:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then _.atDate = {parseDate(args[p.args.date])} _.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not _:processFlagOrCommand(lastArg) -- use the entity ID from the positional arguments if given _.entityID = getEntityId(lastArg, false) if not _.entityID or _.entityID == "" then -- if no positional ID is given, use the eid argument if given _.entityID = getEntityId(args[p.args.eid], true) if _.entityID == "" then -- if eid was explicitly set to empty, then this returns an empty string return "" elseif not _.entityID then -- by default, use the item-entity connected to the current page _.entityID = mw.wikibase.getEntityIdForCurrentPage() end else lastArg = nextArg(args) end _.entity = mw.wikibase.getEntity(_.entityID) _.propertyID = replaceAlias(lastArg) if not _.entity or not _.propertyID then return "" -- we cannot continue without an entity or a property ID end _.propertyID = _.propertyID:upper() if not _.entity.claims or not _.entity.claims[_.propertyID] then return "" -- there is no use to continue without any claims end claims = _.entity.claims[_.propertyID] if _.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > _.states.qualifiersCount then -- claim ID or literal value has been given _.propertyValue = nextArg(args) end for i = 1, _.states.qualifiersCount do -- check if given qualifier ID is an alias and add it _.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif _.states[parameters.reference] then -- do further processing if "reference(s)" command was given _.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if _.propertyValue then _.propertyValue = replaceSpecialChars(_.propertyValue) if _.propertyValue ~= "" and mw.text.trim(_.propertyValue) == "" then _.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else _.propertyValue = mw.text.trim(_.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif _.states.qualifiersCount > 0 then -- "qualifier(s)" command given if _.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif _.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if _.states.qualifiersCount > 0 and not _.states[parameters.property] then _.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if _.states[parameters.reference] and not _.states[parameters.property] and _.states.qualifiersCount == 0 and not _.states[parameters.reference].rawValue then _.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if _.states.qualifiersCount == 1 then _.separators["sep"..parameters.qualifier] = _.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators _:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(_.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and _.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not _.states[parameters.property] then _.states[parameters.property] = State:new(_) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if _.singleClaim then _.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if _.sourcedOnly and not _.states[parameters.reference] then _:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states _:setFormatAndSeparators(_.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to _.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or p.aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end _.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = _:concatValues(_.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if _.editable and value ~= "" then value = value .. _:getEditIcon() end return value end function generalCommand(args, funcName) local _ = Config:new() _.curState = State:new(_) local lastArg local value = nil repeat lastArg = nextArg(args) until not _:processFlag(lastArg) -- use the entity ID from the positional arguments if given _.entityID = getEntityId(lastArg, true) if not _.entityID or _.entityID == "" then -- if no positional ID is given, use the eid argument if given _.entityID = getEntityId(args[p.args.eid], true) if _.entityID == "" then -- if eid was explicitly set to empty, then this returns an empty string return "" elseif not _.entityID then -- by default, use the item-entity connected to the current page _.entityID = mw.wikibase.getEntityIdForCurrentPage() end end if not _.entityID then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = _:getLabel(_.entityID, _.curState.rawValue, _.curState.linked, _.curState.shortName) elseif funcName == p.generalCommands.title then _.inSitelinks = true if _.entityID:sub(1,1) == "Q" then value = mw.wikibase.sitelink(_.entityID) end if _.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then _.entity = mw.wikibase.getEntity(_.entityID) if _.entity.descriptions[_.langCode] then value = _.entity.descriptions[_.langCode].value end else local parsedFormat, formatParams local hooks = {count = 0} _.entity = mw.wikibase.getEntity(_.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then _.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not _.entity.aliases or not _.entity.aliases[_.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = _.entity.aliases[_.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators _:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) _:setFormatAndSeparators(_.curState, parsedFormat) -- iterate to collect values value = _:concatValues(_.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not _.entity.sitelinks or not _.entity.sitelinks[_.siteID] or not _.entity.sitelinks[_.siteID].badges then return "" -- there is no use to continue without any badges end local badges = _.entity.sitelinks[_.siteID].badges _.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators _:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) _:setFormatAndSeparators(_.curState, parsedFormat) -- iterate to collect values value = _:concatValues(_.curState:iterate(badges, hooks)) end end value = value or "" if _.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. _:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) function establishCommands(commandList, commandFunc) for commandIndex, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadSubmodules(frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadSubmodules() return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) local f, args, i, v loadSubmodules(frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p jakrtj7pz4fq6h7as9dp85pn6odsn4p 803513 803512 2019-11-17T11:36:16Z en>Thayts 0 Bug fix 803513 Scribunto text/plain -- Original module located at [[:en:Module:Wd]], [[:en:Module:Wd/i18n]] and [[:en:Module:Wd/aliasesP]]. local p = {} local arg = ... local i18n function loadSubmodules(frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end i18n = i18n or require(title .. "/i18n") p.aliasesP = p.aliasesP or mw.loadData(title .. "/aliasesP") end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", date = "date" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end function replaceAlias(id) if p.aliasesP[id] then id = p.aliasesP[id] end return id end function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end function throwError(errorMessage, param) error(errorText(errorMessage, param)) end function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for i = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for i, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local title = nil local prefix= "" if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id if id:sub(1,1) == "P" then prefix = "Property:" end end prefix = "d:" .. prefix title = label -- may be nil else -- try short name first if requested if short then label = p._property{p.aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.sitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead title = id prefix = "d:Property:" end end if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then return mw.getCurrentFrame():extensionTag("math", datavalue) elseif subtype == 'external-id' and link then local url = p._property{p.aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang then return datavalue['text'], datavalue['language'] elseif datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://tools.wmflabs.org/geohack/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for i, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, p.aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, p.aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag:sub(-1) ~= 's' then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for i, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self.conf:getValue(claim.mainsnak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local version = 1 -- increase this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[p.aliasesP.importedFrom] then statement.snaks[p.aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[p.aliasesP.inferredFrom] then statement.snaks[p.aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[p.aliasesP.typeOfReference] then statement.snaks[p.aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[p.aliasesP.image] then statement.snaks[p.aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, p.aliasesP.language) == self.conf.langName then statement.snaks[p.aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == p.aliasesP.author then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == p.aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- if there are multiple parameter values (authors), add those too for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.sitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.sitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][p.aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][p.aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][p.aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][p.aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, p.aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) else, do some default rendering of name-value pairs, but only if at least "stated in", "reference URL" or "title" is present elseif params[p.aliasesP.statedIn] or params[p.aliasesP.referenceURL] or params[p.aliasesP.title] then citeParams['default'] = {} -- start by adding authors up front if params[p.aliasesP.author] and #params[p.aliasesP.author] > 0 then citeParams['default'][#citeParams['default'] + 1] = table.concat(params[p.aliasesP.author], " & ") end -- combine "reference URL" and "title" into one link if both are present if params[p.aliasesP.referenceURL] and params[p.aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '[' .. params[p.aliasesP.referenceURL][1] .. ' "' .. params[p.aliasesP.title][1] .. '"]' elseif params[p.aliasesP.referenceURL] then citeParams['default'][#citeParams['default'] + 1] = params[p.aliasesP.referenceURL][1] elseif params[p.aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '"' .. params[p.aliasesP.title][1] .. '"' end -- then add "stated in" if params[p.aliasesP.statedIn] then citeParams['default'][#citeParams['default'] + 1] = "''" .. params[p.aliasesP.statedIn][1] .. "''" end -- remove previously added parameters so that they won't be added a second time params[p.aliasesP.author] = nil params[p.aliasesP.referenceURL] = nil params[p.aliasesP.title] = nil params[p.aliasesP.statedIn] = nil -- add the rest of the parameters for i, v in pairs(params) do i = self.conf:getLabel(i) if i ~= "" then citeParams['default'][#citeParams['default'] + 1] = i .. ": " .. v[1] end end value = table.concat(citeParams['default'], "; ") if value ~= "" then value = value .. "." end end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for i, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for i, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.sitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for i, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for i2, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end function getEntityId(arg, allowOmitPropPrefix) if not arg then return nil end if arg:sub(1,9):lower() == "property:" then arg = replaceAlias(mw.text.trim(arg:sub(10))) -- entity ID of a property was given if arg:sub(1,1):upper() ~= "P" then return "" end elseif allowOmitPropPrefix then arg = replaceAlias(arg) -- could be an entity ID of a property without "Property:" prefix elseif arg:sub(1,1):upper() ~= "Q" then return "" end if not mw.wikibase.isValidEntityId(arg) then return "" end return arg:upper() end function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end function claimCommand(args, funcName) local _ = Config:new() _:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then _.atDate = {parseDate(args[p.args.date])} _.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not _:processFlagOrCommand(lastArg) -- use the entity ID from the positional arguments if given _.entityID = getEntityId(lastArg, false) if not _.entityID or _.entityID == "" then -- if no positional ID is given, use the eid argument if given _.entityID = getEntityId(args[p.args.eid], true) if _.entityID == "" then -- if eid was explicitly set to empty, then this returns an empty string return "" elseif not _.entityID then -- by default, use the item-entity connected to the current page _.entityID = mw.wikibase.getEntityIdForCurrentPage() end else lastArg = nextArg(args) end _.entity = mw.wikibase.getEntity(_.entityID) _.propertyID = replaceAlias(lastArg) if not _.entity or not _.propertyID then return "" -- we cannot continue without an entity or a property ID end _.propertyID = _.propertyID:upper() if not _.entity.claims or not _.entity.claims[_.propertyID] then return "" -- there is no use to continue without any claims end claims = _.entity.claims[_.propertyID] if _.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > _.states.qualifiersCount then -- claim ID or literal value has been given _.propertyValue = nextArg(args) end for i = 1, _.states.qualifiersCount do -- check if given qualifier ID is an alias and add it _.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif _.states[parameters.reference] then -- do further processing if "reference(s)" command was given _.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if _.propertyValue then _.propertyValue = replaceSpecialChars(_.propertyValue) if _.propertyValue ~= "" and mw.text.trim(_.propertyValue) == "" then _.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else _.propertyValue = mw.text.trim(_.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif _.states.qualifiersCount > 0 then -- "qualifier(s)" command given if _.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif _.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if _.states.qualifiersCount > 0 and not _.states[parameters.property] then _.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if _.states[parameters.reference] and not _.states[parameters.property] and _.states.qualifiersCount == 0 and not _.states[parameters.reference].rawValue then _.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if _.states.qualifiersCount == 1 then _.separators["sep"..parameters.qualifier] = _.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators _:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(_.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and _.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not _.states[parameters.property] then _.states[parameters.property] = State:new(_) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if _.singleClaim then _.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if _.sourcedOnly and not _.states[parameters.reference] then _:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states _:setFormatAndSeparators(_.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to _.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or p.aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end _.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = _:concatValues(_.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if _.editable and value ~= "" then value = value .. _:getEditIcon() end return value end function generalCommand(args, funcName) local _ = Config:new() _.curState = State:new(_) local lastArg local value = nil repeat lastArg = nextArg(args) until not _:processFlag(lastArg) -- use the entity ID from the positional arguments if given _.entityID = getEntityId(lastArg, true) if not _.entityID or _.entityID == "" then -- if no positional ID is given, use the eid argument if given _.entityID = getEntityId(args[p.args.eid], true) if _.entityID == "" then -- if eid was explicitly set to empty, then this returns an empty string return "" elseif not _.entityID then -- by default, use the item-entity connected to the current page _.entityID = mw.wikibase.getEntityIdForCurrentPage() end end if not _.entityID or not mw.wikibase.entityExists(_.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = _:getLabel(_.entityID, _.curState.rawValue, _.curState.linked, _.curState.shortName) elseif funcName == p.generalCommands.title then _.inSitelinks = true if _.entityID:sub(1,1) == "Q" then value = mw.wikibase.sitelink(_.entityID) end if _.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then _.entity = mw.wikibase.getEntity(_.entityID) if _.entity.descriptions[_.langCode] then value = _.entity.descriptions[_.langCode].value end else local parsedFormat, formatParams local hooks = {count = 0} _.entity = mw.wikibase.getEntity(_.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then _.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not _.entity.aliases or not _.entity.aliases[_.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = _.entity.aliases[_.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators _:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) _:setFormatAndSeparators(_.curState, parsedFormat) -- iterate to collect values value = _:concatValues(_.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not _.entity.sitelinks or not _.entity.sitelinks[_.siteID] or not _.entity.sitelinks[_.siteID].badges then return "" -- there is no use to continue without any badges end local badges = _.entity.sitelinks[_.siteID].badges _.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators _:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) _:setFormatAndSeparators(_.curState, parsedFormat) -- iterate to collect values value = _:concatValues(_.curState:iterate(badges, hooks)) end end value = value or "" if _.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. _:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) function establishCommands(commandList, commandFunc) for commandIndex, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadSubmodules(frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadSubmodules() return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) local f, args, i, v loadSubmodules(frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p 3tqxff3fmdrg8t7kxnn93wow7eeyj1a 803514 803513 2019-11-20T21:54:27Z en>Thayts 0 Replaced underscore variables per talk page 803514 Scribunto text/plain -- Original module located at [[:en:Module:Wd]], [[:en:Module:Wd/i18n]] and [[:en:Module:Wd/aliasesP]]. local p = {} local arg = ... local i18n function loadSubmodules(frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end i18n = i18n or require(title .. "/i18n") p.aliasesP = p.aliasesP or mw.loadData(title .. "/aliasesP") end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", date = "date" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end function replaceAlias(id) if p.aliasesP[id] then id = p.aliasesP[id] end return id end function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end function throwError(errorMessage, param) error(errorText(errorMessage, param)) end function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for i = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for i, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local title = nil local prefix= "" if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id if id:sub(1,1) == "P" then prefix = "Property:" end end prefix = "d:" .. prefix title = label -- may be nil else -- try short name first if requested if short then label = p._property{p.aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.sitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead title = id prefix = "d:Property:" end end if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then return mw.getCurrentFrame():extensionTag("math", datavalue) elseif subtype == 'external-id' and link then local url = p._property{p.aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang then return datavalue['text'], datavalue['language'] elseif datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://tools.wmflabs.org/geohack/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for i, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, p.aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, p.aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag:sub(-1) ~= 's' then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for i, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self.conf:getValue(claim.mainsnak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local version = 1 -- increase this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[p.aliasesP.importedFrom] then statement.snaks[p.aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[p.aliasesP.inferredFrom] then statement.snaks[p.aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[p.aliasesP.typeOfReference] then statement.snaks[p.aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[p.aliasesP.image] then statement.snaks[p.aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, p.aliasesP.language) == self.conf.langName then statement.snaks[p.aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == p.aliasesP.author then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == p.aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- if there are multiple parameter values (authors), add those too for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.sitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.sitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][p.aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][p.aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][p.aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][p.aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, p.aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) else, do some default rendering of name-value pairs, but only if at least "stated in", "reference URL" or "title" is present elseif params[p.aliasesP.statedIn] or params[p.aliasesP.referenceURL] or params[p.aliasesP.title] then citeParams['default'] = {} -- start by adding authors up front if params[p.aliasesP.author] and #params[p.aliasesP.author] > 0 then citeParams['default'][#citeParams['default'] + 1] = table.concat(params[p.aliasesP.author], " & ") end -- combine "reference URL" and "title" into one link if both are present if params[p.aliasesP.referenceURL] and params[p.aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '[' .. params[p.aliasesP.referenceURL][1] .. ' "' .. params[p.aliasesP.title][1] .. '"]' elseif params[p.aliasesP.referenceURL] then citeParams['default'][#citeParams['default'] + 1] = params[p.aliasesP.referenceURL][1] elseif params[p.aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '"' .. params[p.aliasesP.title][1] .. '"' end -- then add "stated in" if params[p.aliasesP.statedIn] then citeParams['default'][#citeParams['default'] + 1] = "''" .. params[p.aliasesP.statedIn][1] .. "''" end -- remove previously added parameters so that they won't be added a second time params[p.aliasesP.author] = nil params[p.aliasesP.referenceURL] = nil params[p.aliasesP.title] = nil params[p.aliasesP.statedIn] = nil -- add the rest of the parameters for i, v in pairs(params) do i = self.conf:getLabel(i) if i ~= "" then citeParams['default'][#citeParams['default'] + 1] = i .. ": " .. v[1] end end value = table.concat(citeParams['default'], "; ") if value ~= "" then value = value .. "." end end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for i, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for i, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.sitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for i, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for i2, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end function getEntityId(arg, allowOmitPropPrefix) if not arg then return nil end if arg:sub(1,9):lower() == "property:" then arg = replaceAlias(mw.text.trim(arg:sub(10))) -- entity ID of a property was given if arg:sub(1,1):upper() ~= "P" then return "" end elseif allowOmitPropPrefix then arg = replaceAlias(arg) -- could be an entity ID of a property without "Property:" prefix elseif arg:sub(1,1):upper() ~= "Q" then return "" end if not mw.wikibase.isValidEntityId(arg) then return "" end return arg:upper() end function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- use the entity ID from the positional arguments if given cfg.entityID = getEntityId(lastArg, false) if not cfg.entityID or cfg.entityID == "" then -- if no positional ID is given, use the eid argument if given cfg.entityID = getEntityId(args[p.args.eid], true) if cfg.entityID == "" then -- if eid was explicitly set to empty, then this returns an empty string return "" elseif not cfg.entityID then -- by default, use the item-entity connected to the current page cfg.entityID = mw.wikibase.getEntityIdForCurrentPage() end else lastArg = nextArg(args) end cfg.entity = mw.wikibase.getEntity(cfg.entityID) cfg.propertyID = replaceAlias(lastArg) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or p.aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- use the entity ID from the positional arguments if given cfg.entityID = getEntityId(lastArg, true) if not cfg.entityID or cfg.entityID == "" then -- if no positional ID is given, use the eid argument if given cfg.entityID = getEntityId(args[p.args.eid], true) if cfg.entityID == "" then -- if eid was explicitly set to empty, then this returns an empty string return "" elseif not cfg.entityID then -- by default, use the item-entity connected to the current page cfg.entityID = mw.wikibase.getEntityIdForCurrentPage() end end if not cfg.entityID or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.sitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then cfg.entity = mw.wikibase.getEntity(cfg.entityID) if cfg.entity.descriptions[cfg.langCode] then value = cfg.entity.descriptions[cfg.langCode].value end else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) function establishCommands(commandList, commandFunc) for commandIndex, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadSubmodules(frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadSubmodules() return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) local f, args, i, v loadSubmodules(frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p 8l7cql13ballf651eg25sb2ppf8gu1i 803515 803514 2019-11-23T12:58:03Z en>Thayts 0 803515 Scribunto text/plain -- Original module located at [[:en:Module:Wd]], [[:en:Module:Wd/i18n]] and [[:en:Module:Wd/aliasesP]]. local p = {} local arg = ... local i18n local function loadSubmodules(frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end i18n = i18n or require(title .. "/i18n") p.aliasesP = p.aliasesP or mw.loadData(title .. "/aliasesP") end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", date = "date" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end local function replaceAlias(id) if p.aliasesP[id] then id = p.aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local title = nil local prefix= "" if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id if id:sub(1,1) == "P" then prefix = "Property:" end end prefix = "d:" .. prefix title = label -- may be nil else -- try short name first if requested if short then label = p._property{p.aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.sitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead title = id prefix = "d:Property:" end end if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then return mw.getCurrentFrame():extensionTag("math", datavalue) elseif subtype == 'external-id' and link then local url = p._property{p.aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang then return datavalue['text'], datavalue['language'] elseif datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://tools.wmflabs.org/geohack/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, p.aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, p.aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag:sub(-1) ~= 's' then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self.conf:getValue(claim.mainsnak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local version = 1 -- increase this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[p.aliasesP.importedFrom] then statement.snaks[p.aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[p.aliasesP.inferredFrom] then statement.snaks[p.aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[p.aliasesP.typeOfReference] then statement.snaks[p.aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[p.aliasesP.image] then statement.snaks[p.aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, p.aliasesP.language) == self.conf.langName then statement.snaks[p.aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == p.aliasesP.author then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == p.aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- if there are multiple parameter values (authors), add those too for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.sitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.sitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][p.aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][p.aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][p.aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][p.aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, p.aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) else, do some default rendering of name-value pairs, but only if at least "stated in", "reference URL" or "title" is present elseif params[p.aliasesP.statedIn] or params[p.aliasesP.referenceURL] or params[p.aliasesP.title] then citeParams['default'] = {} -- start by adding authors up front if params[p.aliasesP.author] and #params[p.aliasesP.author] > 0 then citeParams['default'][#citeParams['default'] + 1] = table.concat(params[p.aliasesP.author], " & ") end -- combine "reference URL" and "title" into one link if both are present if params[p.aliasesP.referenceURL] and params[p.aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '[' .. params[p.aliasesP.referenceURL][1] .. ' "' .. params[p.aliasesP.title][1] .. '"]' elseif params[p.aliasesP.referenceURL] then citeParams['default'][#citeParams['default'] + 1] = params[p.aliasesP.referenceURL][1] elseif params[p.aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '"' .. params[p.aliasesP.title][1] .. '"' end -- then add "stated in" if params[p.aliasesP.statedIn] then citeParams['default'][#citeParams['default'] + 1] = "''" .. params[p.aliasesP.statedIn][1] .. "''" end -- remove previously added parameters so that they won't be added a second time params[p.aliasesP.author] = nil params[p.aliasesP.referenceURL] = nil params[p.aliasesP.title] = nil params[p.aliasesP.statedIn] = nil -- add the rest of the parameters for i, v in pairs(params) do i = self.conf:getLabel(i) if i ~= "" then citeParams['default'][#citeParams['default'] + 1] = i .. ": " .. v[1] end end value = table.concat(citeParams['default'], "; ") if value ~= "" then value = value .. "." end end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.sitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, allowOmitPropPrefix) if not arg then return nil end if arg:sub(1,9):lower() == "property:" then arg = replaceAlias(mw.text.trim(arg:sub(10))) -- entity ID of a property was given if arg:sub(1,1):upper() ~= "P" then return "" end elseif allowOmitPropPrefix then arg = replaceAlias(arg) -- could be an entity ID of a property without "Property:" prefix elseif arg:sub(1,1):upper() ~= "Q" then return "" end if not mw.wikibase.isValidEntityId(arg) then return "" end return arg:upper() end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- use the entity ID from the positional arguments if given cfg.entityID = getEntityId(lastArg, false) if not cfg.entityID or cfg.entityID == "" then -- if no positional ID is given, use the eid argument if given cfg.entityID = getEntityId(args[p.args.eid], true) if cfg.entityID == "" then -- if eid was explicitly set to empty, then this returns an empty string return "" elseif not cfg.entityID then -- by default, use the item-entity connected to the current page cfg.entityID = mw.wikibase.getEntityIdForCurrentPage() end else lastArg = nextArg(args) end cfg.entity = mw.wikibase.getEntity(cfg.entityID) cfg.propertyID = replaceAlias(lastArg) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or p.aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- use the entity ID from the positional arguments if given cfg.entityID = getEntityId(lastArg, true) if not cfg.entityID or cfg.entityID == "" then -- if no positional ID is given, use the eid argument if given cfg.entityID = getEntityId(args[p.args.eid], true) if cfg.entityID == "" then -- if eid was explicitly set to empty, then this returns an empty string return "" elseif not cfg.entityID then -- by default, use the item-entity connected to the current page cfg.entityID = mw.wikibase.getEntityIdForCurrentPage() end end if not cfg.entityID or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.sitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then cfg.entity = mw.wikibase.getEntity(cfg.entityID) if cfg.entity.descriptions[cfg.langCode] then value = cfg.entity.descriptions[cfg.langCode].value end else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadSubmodules(frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadSubmodules() return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) local f, args loadSubmodules(frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p 2xomx38gx67wbrc4djychq19i0w3qky 803516 803515 2020-01-17T14:52:37Z en>Thayts 0 Added 'qid' attribute for math tags per request at talk 803516 Scribunto text/plain -- Original module located at [[:en:Module:Wd]], [[:en:Module:Wd/i18n]] and [[:en:Module:Wd/aliasesP]]. local p = {} local arg = ... local i18n local function loadSubmodules(frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end i18n = i18n or require(title .. "/i18n") p.aliasesP = p.aliasesP or mw.loadData(title .. "/aliasesP") end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", date = "date" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end local function replaceAlias(id) if p.aliasesP[id] then id = p.aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local title = nil local prefix= "" if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id if id:sub(1,1) == "P" then prefix = "Property:" end end prefix = "d:" .. prefix title = label -- may be nil else -- try short name first if requested if short then label = p._property{p.aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.sitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead title = id prefix = "d:Property:" end end if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, isProperty) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (isProperty or self.propertyID == p.aliasesP.hasPart) and snak.property == p.aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{p.aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang then return datavalue['text'], datavalue['language'] elseif datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://tools.wmflabs.org/geohack/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, p.aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, p.aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag:sub(-1) ~= 's' then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self.conf:getValue(claim.mainsnak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, true)} -- create one value object; isProperty = true if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local version = 1 -- increase this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[p.aliasesP.importedFrom] then statement.snaks[p.aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[p.aliasesP.inferredFrom] then statement.snaks[p.aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[p.aliasesP.typeOfReference] then statement.snaks[p.aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[p.aliasesP.image] then statement.snaks[p.aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, p.aliasesP.language) == self.conf.langName then statement.snaks[p.aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == p.aliasesP.author then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == p.aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- if there are multiple parameter values (authors), add those too for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.sitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.sitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][p.aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][p.aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][p.aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][p.aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, p.aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) else, do some default rendering of name-value pairs, but only if at least "stated in", "reference URL" or "title" is present elseif params[p.aliasesP.statedIn] or params[p.aliasesP.referenceURL] or params[p.aliasesP.title] then citeParams['default'] = {} -- start by adding authors up front if params[p.aliasesP.author] and #params[p.aliasesP.author] > 0 then citeParams['default'][#citeParams['default'] + 1] = table.concat(params[p.aliasesP.author], " & ") end -- combine "reference URL" and "title" into one link if both are present if params[p.aliasesP.referenceURL] and params[p.aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '[' .. params[p.aliasesP.referenceURL][1] .. ' "' .. params[p.aliasesP.title][1] .. '"]' elseif params[p.aliasesP.referenceURL] then citeParams['default'][#citeParams['default'] + 1] = params[p.aliasesP.referenceURL][1] elseif params[p.aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '"' .. params[p.aliasesP.title][1] .. '"' end -- then add "stated in" if params[p.aliasesP.statedIn] then citeParams['default'][#citeParams['default'] + 1] = "''" .. params[p.aliasesP.statedIn][1] .. "''" end -- remove previously added parameters so that they won't be added a second time params[p.aliasesP.author] = nil params[p.aliasesP.referenceURL] = nil params[p.aliasesP.title] = nil params[p.aliasesP.statedIn] = nil -- add the rest of the parameters for i, v in pairs(params) do i = self.conf:getLabel(i) if i ~= "" then citeParams['default'][#citeParams['default'] + 1] = i .. ": " .. v[1] end end value = table.concat(citeParams['default'], "; ") if value ~= "" then value = value .. "." end end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.sitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, allowOmitPropPrefix) if not arg then return nil end if arg:sub(1,9):lower() == "property:" then arg = replaceAlias(mw.text.trim(arg:sub(10))) -- entity ID of a property was given if arg:sub(1,1):upper() ~= "P" then return "" end elseif allowOmitPropPrefix then arg = replaceAlias(arg) -- could be an entity ID of a property without "Property:" prefix elseif arg:sub(1,1):upper() ~= "Q" then return "" end if not mw.wikibase.isValidEntityId(arg) then return "" end return arg:upper() end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- use the entity ID from the positional arguments if given cfg.entityID = getEntityId(lastArg, false) if not cfg.entityID or cfg.entityID == "" then -- if no positional ID is given, use the eid argument if given cfg.entityID = getEntityId(args[p.args.eid], true) if cfg.entityID == "" then -- if eid was explicitly set to empty, then this returns an empty string return "" elseif not cfg.entityID then -- by default, use the item-entity connected to the current page cfg.entityID = mw.wikibase.getEntityIdForCurrentPage() end else lastArg = nextArg(args) end cfg.entity = mw.wikibase.getEntity(cfg.entityID) cfg.propertyID = replaceAlias(lastArg) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or p.aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- use the entity ID from the positional arguments if given cfg.entityID = getEntityId(lastArg, true) if not cfg.entityID or cfg.entityID == "" then -- if no positional ID is given, use the eid argument if given cfg.entityID = getEntityId(args[p.args.eid], true) if cfg.entityID == "" then -- if eid was explicitly set to empty, then this returns an empty string return "" elseif not cfg.entityID then -- by default, use the item-entity connected to the current page cfg.entityID = mw.wikibase.getEntityIdForCurrentPage() end end if not cfg.entityID or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.sitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then cfg.entity = mw.wikibase.getEntity(cfg.entityID) if cfg.entity.descriptions[cfg.langCode] then value = cfg.entity.descriptions[cfg.langCode].value end else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadSubmodules(frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadSubmodules() return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) local f, args loadSubmodules(frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p d9nvllyn4rtpqoojjox16qkwzl8zdgg 803517 803516 2020-01-18T10:59:51Z en>Thayts 0 Moved function 'getValue' to State to be able to get its type and added type check for math tags 803517 Scribunto text/plain -- Original module located at [[:en:Module:Wd]], [[:en:Module:Wd/i18n]] and [[:en:Module:Wd/aliasesP]]. local p = {} local arg = ... local i18n local function loadSubmodules(frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end i18n = i18n or require(title .. "/i18n") p.aliasesP = p.aliasesP or mw.loadData(title .. "/aliasesP") end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", date = "date" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end local function replaceAlias(id) if p.aliasesP[id] then id = p.aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local title = nil local prefix= "" if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id if id:sub(1,1) == "P" then prefix = "Property:" end end prefix = "d:" .. prefix title = label -- may be nil else -- try short name first if requested if short then label = p._property{p.aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.sitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead title = id prefix = "d:Property:" end end if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (self.type == parameters.property or (self.conf.propertyID == p.aliasesP.hasPart and self.type:sub(1,2) == parameters.qualifier)) and snak.property == p.aliasesP.definingFormula then attribute = {qid = self.conf.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{p.aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang then return datavalue['text'], datavalue['language'] elseif datavalue['language'] == self.conf.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self.conf:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.conf.langObj:formatDate("F", "1-"..m.."-1") if d then if self.conf.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://tools.wmflabs.org/geohack/geohack.php?language="..self.conf.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self.conf:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function State:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function State:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function State:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function State:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.conf.periods[1] and self.conf.periods[2] and self.conf.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, p.aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, p.aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.conf.periods[1] then -- future if startTimeY and datePrecedesDate(self.conf.atDate[1], self.conf.atDate[2], self.conf.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.conf.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.conf.atDate[1], self.conf.atDate[2], self.conf.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.conf.atDate[1], self.conf.atDate[2], self.conf.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.conf.periods[3] then -- former if endTimeY and not datePrecedesDate(self.conf.atDate[1], self.conf.atDate[2], self.conf.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag:sub(-1) ~= 's' then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local version = 1 -- increase this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[p.aliasesP.importedFrom] then statement.snaks[p.aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[p.aliasesP.inferredFrom] then statement.snaks[p.aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[p.aliasesP.typeOfReference] then statement.snaks[p.aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[p.aliasesP.image] then statement.snaks[p.aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, p.aliasesP.language) == self.conf.langName then statement.snaks[p.aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == p.aliasesP.author then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == p.aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- if there are multiple parameter values (authors), add those too for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.sitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.sitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][p.aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][p.aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][p.aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][p.aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, p.aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) else, do some default rendering of name-value pairs, but only if at least "stated in", "reference URL" or "title" is present elseif params[p.aliasesP.statedIn] or params[p.aliasesP.referenceURL] or params[p.aliasesP.title] then citeParams['default'] = {} -- start by adding authors up front if params[p.aliasesP.author] and #params[p.aliasesP.author] > 0 then citeParams['default'][#citeParams['default'] + 1] = table.concat(params[p.aliasesP.author], " & ") end -- combine "reference URL" and "title" into one link if both are present if params[p.aliasesP.referenceURL] and params[p.aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '[' .. params[p.aliasesP.referenceURL][1] .. ' "' .. params[p.aliasesP.title][1] .. '"]' elseif params[p.aliasesP.referenceURL] then citeParams['default'][#citeParams['default'] + 1] = params[p.aliasesP.referenceURL][1] elseif params[p.aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '"' .. params[p.aliasesP.title][1] .. '"' end -- then add "stated in" if params[p.aliasesP.statedIn] then citeParams['default'][#citeParams['default'] + 1] = "''" .. params[p.aliasesP.statedIn][1] .. "''" end -- remove previously added parameters so that they won't be added a second time params[p.aliasesP.author] = nil params[p.aliasesP.referenceURL] = nil params[p.aliasesP.title] = nil params[p.aliasesP.statedIn] = nil -- add the rest of the parameters for i, v in pairs(params) do i = self.conf:getLabel(i) if i ~= "" then citeParams['default'][#citeParams['default'] + 1] = i .. ": " .. v[1] end end value = table.concat(citeParams['default'], "; ") if value ~= "" then value = value .. "." end end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.sitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, allowOmitPropPrefix) if not arg then return nil end if arg:sub(1,9):lower() == "property:" then arg = replaceAlias(mw.text.trim(arg:sub(10))) -- entity ID of a property was given if arg:sub(1,1):upper() ~= "P" then return "" end elseif allowOmitPropPrefix then arg = replaceAlias(arg) -- could be an entity ID of a property without "Property:" prefix elseif arg:sub(1,1):upper() ~= "Q" then return "" end if not mw.wikibase.isValidEntityId(arg) then return "" end return arg:upper() end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- use the entity ID from the positional arguments if given cfg.entityID = getEntityId(lastArg, false) if not cfg.entityID or cfg.entityID == "" then -- if no positional ID is given, use the eid argument if given cfg.entityID = getEntityId(args[p.args.eid], true) if cfg.entityID == "" then -- if eid was explicitly set to empty, then this returns an empty string return "" elseif not cfg.entityID then -- by default, use the item-entity connected to the current page cfg.entityID = mw.wikibase.getEntityIdForCurrentPage() end else lastArg = nextArg(args) end cfg.entity = mw.wikibase.getEntity(cfg.entityID) cfg.propertyID = replaceAlias(lastArg) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or p.aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- use the entity ID from the positional arguments if given cfg.entityID = getEntityId(lastArg, true) if not cfg.entityID or cfg.entityID == "" then -- if no positional ID is given, use the eid argument if given cfg.entityID = getEntityId(args[p.args.eid], true) if cfg.entityID == "" then -- if eid was explicitly set to empty, then this returns an empty string return "" elseif not cfg.entityID then -- by default, use the item-entity connected to the current page cfg.entityID = mw.wikibase.getEntityIdForCurrentPage() end end if not cfg.entityID or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.sitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then cfg.entity = mw.wikibase.getEntity(cfg.entityID) if cfg.entity.descriptions[cfg.langCode] then value = cfg.entity.descriptions[cfg.langCode].value end else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadSubmodules(frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadSubmodules() return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) local f, args loadSubmodules(frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p elq9sykeolm2h8sxtxmmd1dzw4llshl 803518 803517 2020-01-18T19:25:42Z en>Thayts 0 Cleaner solution, partly undoing previous edit 803518 Scribunto text/plain -- Original module located at [[:en:Module:Wd]], [[:en:Module:Wd/i18n]] and [[:en:Module:Wd/aliasesP]]. local p = {} local arg = ... local i18n local function loadSubmodules(frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end i18n = i18n or require(title .. "/i18n") p.aliasesP = p.aliasesP or mw.loadData(title .. "/aliasesP") end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", date = "date" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end local function replaceAlias(id) if p.aliasesP[id] then id = p.aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local title = nil local prefix= "" if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id if id:sub(1,1) == "P" then prefix = "Property:" end end prefix = "d:" .. prefix title = label -- may be nil else -- try short name first if requested if short then label = p._property{p.aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.sitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead title = id prefix = "d:Property:" end end if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == p.aliasesP.hasPart)) and snak.property == p.aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{p.aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang then return datavalue['text'], datavalue['language'] elseif datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://tools.wmflabs.org/geohack/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, p.aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, p.aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag:sub(-1) ~= 's' then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local version = 1 -- increase this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[p.aliasesP.importedFrom] then statement.snaks[p.aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[p.aliasesP.inferredFrom] then statement.snaks[p.aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[p.aliasesP.typeOfReference] then statement.snaks[p.aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[p.aliasesP.image] then statement.snaks[p.aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, p.aliasesP.language) == self.conf.langName then statement.snaks[p.aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == p.aliasesP.author then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == p.aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- if there are multiple parameter values (authors), add those too for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.sitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.sitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][p.aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][p.aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][p.aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][p.aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, p.aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) else, do some default rendering of name-value pairs, but only if at least "stated in", "reference URL" or "title" is present elseif params[p.aliasesP.statedIn] or params[p.aliasesP.referenceURL] or params[p.aliasesP.title] then citeParams['default'] = {} -- start by adding authors up front if params[p.aliasesP.author] and #params[p.aliasesP.author] > 0 then citeParams['default'][#citeParams['default'] + 1] = table.concat(params[p.aliasesP.author], " & ") end -- combine "reference URL" and "title" into one link if both are present if params[p.aliasesP.referenceURL] and params[p.aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '[' .. params[p.aliasesP.referenceURL][1] .. ' "' .. params[p.aliasesP.title][1] .. '"]' elseif params[p.aliasesP.referenceURL] then citeParams['default'][#citeParams['default'] + 1] = params[p.aliasesP.referenceURL][1] elseif params[p.aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '"' .. params[p.aliasesP.title][1] .. '"' end -- then add "stated in" if params[p.aliasesP.statedIn] then citeParams['default'][#citeParams['default'] + 1] = "''" .. params[p.aliasesP.statedIn][1] .. "''" end -- remove previously added parameters so that they won't be added a second time params[p.aliasesP.author] = nil params[p.aliasesP.referenceURL] = nil params[p.aliasesP.title] = nil params[p.aliasesP.statedIn] = nil -- add the rest of the parameters for i, v in pairs(params) do i = self.conf:getLabel(i) if i ~= "" then citeParams['default'][#citeParams['default'] + 1] = i .. ": " .. v[1] end end value = table.concat(citeParams['default'], "; ") if value ~= "" then value = value .. "." end end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.sitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, allowOmitPropPrefix) if not arg then return nil end if arg:sub(1,9):lower() == "property:" then arg = replaceAlias(mw.text.trim(arg:sub(10))) -- entity ID of a property was given if arg:sub(1,1):upper() ~= "P" then return "" end elseif allowOmitPropPrefix then arg = replaceAlias(arg) -- could be an entity ID of a property without "Property:" prefix elseif arg:sub(1,1):upper() ~= "Q" then return "" end if not mw.wikibase.isValidEntityId(arg) then return "" end return arg:upper() end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- use the entity ID from the positional arguments if given cfg.entityID = getEntityId(lastArg, false) if not cfg.entityID or cfg.entityID == "" then -- if no positional ID is given, use the eid argument if given cfg.entityID = getEntityId(args[p.args.eid], true) if cfg.entityID == "" then -- if eid was explicitly set to empty, then this returns an empty string return "" elseif not cfg.entityID then -- by default, use the item-entity connected to the current page cfg.entityID = mw.wikibase.getEntityIdForCurrentPage() end else lastArg = nextArg(args) end cfg.entity = mw.wikibase.getEntity(cfg.entityID) cfg.propertyID = replaceAlias(lastArg) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or p.aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- use the entity ID from the positional arguments if given cfg.entityID = getEntityId(lastArg, true) if not cfg.entityID or cfg.entityID == "" then -- if no positional ID is given, use the eid argument if given cfg.entityID = getEntityId(args[p.args.eid], true) if cfg.entityID == "" then -- if eid was explicitly set to empty, then this returns an empty string return "" elseif not cfg.entityID then -- by default, use the item-entity connected to the current page cfg.entityID = mw.wikibase.getEntityIdForCurrentPage() end end if not cfg.entityID or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.sitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then cfg.entity = mw.wikibase.getEntity(cfg.entityID) if cfg.entity.descriptions[cfg.langCode] then value = cfg.entity.descriptions[cfg.langCode].value end else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadSubmodules(frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadSubmodules() return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) local f, args loadSubmodules(frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p bh9mwh8i2dr0jt8vezp4ieyslhm22zf 803519 803518 2020-02-01T16:26:53Z en>Thayts 0 Added new named argument 'page' 803519 Scribunto text/plain -- Original module located at [[:en:Module:Wd]], [[:en:Module:Wd/i18n]] and [[:en:Module:Wd/aliasesP]]. local p = {} local arg = ... local i18n local function loadSubmodules(frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end i18n = i18n or require(title .. "/i18n") p.aliasesP = p.aliasesP or mw.loadData(title .. "/aliasesP") end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end local function replaceAlias(id) if p.aliasesP[id] then id = p.aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local title = nil local prefix= "" if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id if id:sub(1,1) == "P" then prefix = "Property:" end end prefix = "d:" .. prefix title = label -- may be nil else -- try short name first if requested if short then label = p._property{p.aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.sitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead title = id prefix = "d:Property:" end end if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == p.aliasesP.hasPart)) and snak.property == p.aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{p.aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang then return datavalue['text'], datavalue['language'] elseif datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://tools.wmflabs.org/geohack/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, p.aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, p.aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag:sub(-1) ~= 's' then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local version = 1 -- increase this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[p.aliasesP.importedFrom] then statement.snaks[p.aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[p.aliasesP.inferredFrom] then statement.snaks[p.aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[p.aliasesP.typeOfReference] then statement.snaks[p.aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[p.aliasesP.image] then statement.snaks[p.aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, p.aliasesP.language) == self.conf.langName then statement.snaks[p.aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == p.aliasesP.author then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == p.aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- if there are multiple parameter values (authors), add those too for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.sitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.sitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][p.aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][p.aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][p.aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][p.aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, p.aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) else, do some default rendering of name-value pairs, but only if at least "stated in", "reference URL" or "title" is present elseif params[p.aliasesP.statedIn] or params[p.aliasesP.referenceURL] or params[p.aliasesP.title] then citeParams['default'] = {} -- start by adding authors up front if params[p.aliasesP.author] and #params[p.aliasesP.author] > 0 then citeParams['default'][#citeParams['default'] + 1] = table.concat(params[p.aliasesP.author], " & ") end -- combine "reference URL" and "title" into one link if both are present if params[p.aliasesP.referenceURL] and params[p.aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '[' .. params[p.aliasesP.referenceURL][1] .. ' "' .. params[p.aliasesP.title][1] .. '"]' elseif params[p.aliasesP.referenceURL] then citeParams['default'][#citeParams['default'] + 1] = params[p.aliasesP.referenceURL][1] elseif params[p.aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '"' .. params[p.aliasesP.title][1] .. '"' end -- then add "stated in" if params[p.aliasesP.statedIn] then citeParams['default'][#citeParams['default'] + 1] = "''" .. params[p.aliasesP.statedIn][1] .. "''" end -- remove previously added parameters so that they won't be added a second time params[p.aliasesP.author] = nil params[p.aliasesP.referenceURL] = nil params[p.aliasesP.title] = nil params[p.aliasesP.statedIn] = nil -- add the rest of the parameters for i, v in pairs(params) do i = self.conf:getLabel(i) if i ~= "" then citeParams['default'][#citeParams['default'] + 1] = i .. ": " .. v[1] end end value = table.concat(citeParams['default'], "; ") if value ~= "" then value = value .. "." end end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.sitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or p.aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.sitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then cfg.entity = mw.wikibase.getEntity(cfg.entityID) if cfg.entity.descriptions[cfg.langCode] then value = cfg.entity.descriptions[cfg.langCode].value end else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadSubmodules(frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadSubmodules() return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) local f, args loadSubmodules(frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p kca4kbz167j11gju3a59pq3rwa1u0if 803520 803519 2020-04-18T22:39:18Z en>Thayts 0 Copy from sandbox: removed language return value for monolingualtext which is no longer used; made explicit check on singular commands (per edit request) 803520 Scribunto text/plain -- Original module located at [[:en:Module:Wd]], [[:en:Module:Wd/i18n]] and [[:en:Module:Wd/aliasesP]]. local p = {} local arg = ... local i18n local function loadSubmodules(frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end i18n = i18n or require(title .. "/i18n") p.aliasesP = p.aliasesP or mw.loadData(title .. "/aliasesP") end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end local function replaceAlias(id) if p.aliasesP[id] then id = p.aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local title = nil local prefix= "" if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id if id:sub(1,1) == "P" then prefix = "Property:" end end prefix = "d:" .. prefix title = label -- may be nil else -- try short name first if requested if short then label = p._property{p.aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.sitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead title = id prefix = "d:Property:" end end if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == p.aliasesP.hasPart)) and snak.property == p.aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{p.aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://tools.wmflabs.org/geohack/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, p.aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, p.aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local version = 1 -- increase this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[p.aliasesP.importedFrom] then statement.snaks[p.aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[p.aliasesP.inferredFrom] then statement.snaks[p.aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[p.aliasesP.typeOfReference] then statement.snaks[p.aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[p.aliasesP.image] then statement.snaks[p.aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, p.aliasesP.language) == self.conf.langName then statement.snaks[p.aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == p.aliasesP.author then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == p.aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- if there are multiple parameter values (authors), add those too for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.sitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.sitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][p.aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][p.aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][p.aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][p.aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, p.aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) else, do some default rendering of name-value pairs, but only if at least "stated in", "reference URL" or "title" is present elseif params[p.aliasesP.statedIn] or params[p.aliasesP.referenceURL] or params[p.aliasesP.title] then citeParams['default'] = {} -- start by adding authors up front if params[p.aliasesP.author] and #params[p.aliasesP.author] > 0 then citeParams['default'][#citeParams['default'] + 1] = table.concat(params[p.aliasesP.author], " & ") end -- combine "reference URL" and "title" into one link if both are present if params[p.aliasesP.referenceURL] and params[p.aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '[' .. params[p.aliasesP.referenceURL][1] .. ' "' .. params[p.aliasesP.title][1] .. '"]' elseif params[p.aliasesP.referenceURL] then citeParams['default'][#citeParams['default'] + 1] = params[p.aliasesP.referenceURL][1] elseif params[p.aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '"' .. params[p.aliasesP.title][1] .. '"' end -- then add "stated in" if params[p.aliasesP.statedIn] then citeParams['default'][#citeParams['default'] + 1] = "''" .. params[p.aliasesP.statedIn][1] .. "''" end -- remove previously added parameters so that they won't be added a second time params[p.aliasesP.author] = nil params[p.aliasesP.referenceURL] = nil params[p.aliasesP.title] = nil params[p.aliasesP.statedIn] = nil -- add the rest of the parameters for i, v in pairs(params) do i = self.conf:getLabel(i) if i ~= "" then citeParams['default'][#citeParams['default'] + 1] = i .. ": " .. v[1] end end value = table.concat(citeParams['default'], "; ") if value ~= "" then value = value .. "." end end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.sitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or p.aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.sitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then cfg.entity = mw.wikibase.getEntity(cfg.entityID) if cfg.entity.descriptions[cfg.langCode] then value = cfg.entity.descriptions[cfg.langCode].value end else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadSubmodules(frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadSubmodules() return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) local f, args loadSubmodules(frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p catghfkahlnvvds3za0wpqvy1l24vzg 803521 803520 2020-04-19T10:39:11Z en>Thayts 0 Assimilating aliasesP back into the main module. IMPORTANT UPDATE INSTRUCTIONS: In order to prevent errors while updating this module on your local wiki, follow these steps: #1. Edit your Module:Wd/i18n and add the line "function p.init() return p end" right before the last line (above "return p"); #2. Copy the contents of [[:en:Module:Wd]] to your local module; #3. Copy the contents of [[:en:Module:Wd/i18n]] to your local submodule; #4. You can safely delete Module:Wd/aliasesP now. 803521 Scribunto text/plain -- Original module located at [[:en:Module:Wd]], [[:en:Module:Wd/i18n]] and [[:en:Module:Wd/aliasesP]]. local p = {} local arg = ... local i18n local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18n = require(title .. "/i18n") i18n = i18n.init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", publisher = "P123", importedFrom = "P143", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local title = nil local prefix= "" if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id if id:sub(1,1) == "P" then prefix = "Property:" end end prefix = "d:" .. prefix title = label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.sitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead title = id prefix = "d:Property:" end end if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://tools.wmflabs.org/geohack/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local version = 1 -- increase this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[aliasesP.importedFrom] then statement.snaks[aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[aliasesP.inferredFrom] then statement.snaks[aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == aliasesP.author then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- if there are multiple parameter values (authors), add those too for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.sitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.sitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) else, do some default rendering of name-value pairs, but only if at least "stated in", "reference URL" or "title" is present elseif params[aliasesP.statedIn] or params[aliasesP.referenceURL] or params[aliasesP.title] then citeParams['default'] = {} -- start by adding authors up front if params[aliasesP.author] and #params[aliasesP.author] > 0 then citeParams['default'][#citeParams['default'] + 1] = table.concat(params[aliasesP.author], " & ") end -- combine "reference URL" and "title" into one link if both are present if params[aliasesP.referenceURL] and params[aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '[' .. params[aliasesP.referenceURL][1] .. ' "' .. params[aliasesP.title][1] .. '"]' elseif params[aliasesP.referenceURL] then citeParams['default'][#citeParams['default'] + 1] = params[aliasesP.referenceURL][1] elseif params[aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '"' .. params[aliasesP.title][1] .. '"' end -- then add "stated in" if params[aliasesP.statedIn] then citeParams['default'][#citeParams['default'] + 1] = "''" .. params[aliasesP.statedIn][1] .. "''" end -- remove previously added parameters so that they won't be added a second time params[aliasesP.author] = nil params[aliasesP.referenceURL] = nil params[aliasesP.title] = nil params[aliasesP.statedIn] = nil -- add the rest of the parameters for i, v in pairs(params) do i = self.conf:getLabel(i) if i ~= "" then citeParams['default'][#citeParams['default'] + 1] = i .. ": " .. v[1] end end value = table.concat(citeParams['default'], "; ") if value ~= "" then value = value .. "." end end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.sitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.sitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then cfg.entity = mw.wikibase.getEntity(cfg.entityID) if cfg.entity.descriptions[cfg.langCode] then value = cfg.entity.descriptions[cfg.langCode].value end else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p j761o05rl6453pzayi53ibb0pmpqfcn 803522 803521 2020-04-19T10:41:20Z en>Thayts 0 803522 Scribunto text/plain -- Original module located at [[:en:Module:Wd]], [[:en:Module:Wd/i18n]]. local p = {} local arg = ... local i18n local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18n = require(title .. "/i18n") i18n = i18n.init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", publisher = "P123", importedFrom = "P143", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local title = nil local prefix= "" if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id if id:sub(1,1) == "P" then prefix = "Property:" end end prefix = "d:" .. prefix title = label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.sitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead title = id prefix = "d:Property:" end end if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://tools.wmflabs.org/geohack/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local version = 1 -- increase this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[aliasesP.importedFrom] then statement.snaks[aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[aliasesP.inferredFrom] then statement.snaks[aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == aliasesP.author then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- if there are multiple parameter values (authors), add those too for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.sitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.sitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) else, do some default rendering of name-value pairs, but only if at least "stated in", "reference URL" or "title" is present elseif params[aliasesP.statedIn] or params[aliasesP.referenceURL] or params[aliasesP.title] then citeParams['default'] = {} -- start by adding authors up front if params[aliasesP.author] and #params[aliasesP.author] > 0 then citeParams['default'][#citeParams['default'] + 1] = table.concat(params[aliasesP.author], " & ") end -- combine "reference URL" and "title" into one link if both are present if params[aliasesP.referenceURL] and params[aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '[' .. params[aliasesP.referenceURL][1] .. ' "' .. params[aliasesP.title][1] .. '"]' elseif params[aliasesP.referenceURL] then citeParams['default'][#citeParams['default'] + 1] = params[aliasesP.referenceURL][1] elseif params[aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '"' .. params[aliasesP.title][1] .. '"' end -- then add "stated in" if params[aliasesP.statedIn] then citeParams['default'][#citeParams['default'] + 1] = "''" .. params[aliasesP.statedIn][1] .. "''" end -- remove previously added parameters so that they won't be added a second time params[aliasesP.author] = nil params[aliasesP.referenceURL] = nil params[aliasesP.title] = nil params[aliasesP.statedIn] = nil -- add the rest of the parameters for i, v in pairs(params) do i = self.conf:getLabel(i) if i ~= "" then citeParams['default'][#citeParams['default'] + 1] = i .. ": " .. v[1] end end value = table.concat(citeParams['default'], "; ") if value ~= "" then value = value .. "." end end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.sitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.sitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then cfg.entity = mw.wikibase.getEntity(cfg.entityID) if cfg.entity.descriptions[cfg.langCode] then value = cfg.entity.descriptions[cfg.langCode].value end else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p h92g70uxhopoqrswthpm2zvgqs0shuf 803523 803522 2020-04-19T10:41:33Z en>Thayts 0 803523 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. local p = {} local arg = ... local i18n local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18n = require(title .. "/i18n") i18n = i18n.init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", publisher = "P123", importedFrom = "P143", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local title = nil local prefix= "" if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id if id:sub(1,1) == "P" then prefix = "Property:" end end prefix = "d:" .. prefix title = label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.sitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead title = id prefix = "d:Property:" end end if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://tools.wmflabs.org/geohack/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local version = 1 -- increase this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[aliasesP.importedFrom] then statement.snaks[aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[aliasesP.inferredFrom] then statement.snaks[aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == aliasesP.author then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- if there are multiple parameter values (authors), add those too for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.sitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.sitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) else, do some default rendering of name-value pairs, but only if at least "stated in", "reference URL" or "title" is present elseif params[aliasesP.statedIn] or params[aliasesP.referenceURL] or params[aliasesP.title] then citeParams['default'] = {} -- start by adding authors up front if params[aliasesP.author] and #params[aliasesP.author] > 0 then citeParams['default'][#citeParams['default'] + 1] = table.concat(params[aliasesP.author], " & ") end -- combine "reference URL" and "title" into one link if both are present if params[aliasesP.referenceURL] and params[aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '[' .. params[aliasesP.referenceURL][1] .. ' "' .. params[aliasesP.title][1] .. '"]' elseif params[aliasesP.referenceURL] then citeParams['default'][#citeParams['default'] + 1] = params[aliasesP.referenceURL][1] elseif params[aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '"' .. params[aliasesP.title][1] .. '"' end -- then add "stated in" if params[aliasesP.statedIn] then citeParams['default'][#citeParams['default'] + 1] = "''" .. params[aliasesP.statedIn][1] .. "''" end -- remove previously added parameters so that they won't be added a second time params[aliasesP.author] = nil params[aliasesP.referenceURL] = nil params[aliasesP.title] = nil params[aliasesP.statedIn] = nil -- add the rest of the parameters for i, v in pairs(params) do i = self.conf:getLabel(i) if i ~= "" then citeParams['default'][#citeParams['default'] + 1] = i .. ": " .. v[1] end end value = table.concat(citeParams['default'], "; ") if value ~= "" then value = value .. "." end end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.sitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.sitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then cfg.entity = mw.wikibase.getEntity(cfg.entityID) if cfg.entity.descriptions[cfg.langCode] then value = cfg.entity.descriptions[cfg.langCode].value end else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p eiuoystn7fr17k1kc2eylks1ln1n37n 803524 803523 2020-04-19T11:10:43Z en>Thayts 0 ADDITIONAL NOTE: Copy the structure of [[:en:Module:Wd/i18n]], but of course leave your local translations intact! 803524 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. local p = {} local arg = ... local i18n local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18n = require(title .. "/i18n") i18n = i18n.init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", publisher = "P123", importedFrom = "P143", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local title = nil local prefix= "" if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id if id:sub(1,1) == "P" then prefix = "Property:" end end prefix = "d:" .. prefix title = label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.sitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead title = id prefix = "d:Property:" end end if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://tools.wmflabs.org/geohack/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local version = 1 -- increment this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[aliasesP.importedFrom] then statement.snaks[aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[aliasesP.inferredFrom] then statement.snaks[aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == aliasesP.author then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- if there are multiple parameter values (authors), add those too for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.sitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.sitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) else, do some default rendering of name-value pairs, but only if at least "stated in", "reference URL" or "title" is present elseif params[aliasesP.statedIn] or params[aliasesP.referenceURL] or params[aliasesP.title] then citeParams['default'] = {} -- start by adding authors up front if params[aliasesP.author] and #params[aliasesP.author] > 0 then citeParams['default'][#citeParams['default'] + 1] = table.concat(params[aliasesP.author], " & ") end -- combine "reference URL" and "title" into one link if both are present if params[aliasesP.referenceURL] and params[aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '[' .. params[aliasesP.referenceURL][1] .. ' "' .. params[aliasesP.title][1] .. '"]' elseif params[aliasesP.referenceURL] then citeParams['default'][#citeParams['default'] + 1] = params[aliasesP.referenceURL][1] elseif params[aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '"' .. params[aliasesP.title][1] .. '"' end -- then add "stated in" if params[aliasesP.statedIn] then citeParams['default'][#citeParams['default'] + 1] = "''" .. params[aliasesP.statedIn][1] .. "''" end -- remove previously added parameters so that they won't be added a second time params[aliasesP.author] = nil params[aliasesP.referenceURL] = nil params[aliasesP.title] = nil params[aliasesP.statedIn] = nil -- add the rest of the parameters for i, v in pairs(params) do i = self.conf:getLabel(i) if i ~= "" then citeParams['default'][#citeParams['default'] + 1] = i .. ": " .. v[1] end end value = table.concat(citeParams['default'], "; ") if value ~= "" then value = value .. "." end end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.sitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.sitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then cfg.entity = mw.wikibase.getEntity(cfg.entityID) if cfg.entity.descriptions[cfg.langCode] then value = cfg.entity.descriptions[cfg.langCode].value end else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p gdo1m2obqkrimz2yglllqsl7z8r4wy4 803525 803524 2020-04-25T18:00:06Z en>Thayts 0 Updated to latest Wikibase API 803525 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. local p = {} local arg = ... local i18n local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18n = require(title .. "/i18n") i18n = i18n.init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", publisher = "P123", importedFrom = "P143", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local title = nil local prefix= "" if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id if id:sub(1,1) == "P" then prefix = "Property:" end end prefix = "d:" .. prefix title = label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead title = id prefix = "d:Property:" end end if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://tools.wmflabs.org/geohack/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local version = 1 -- increment this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[aliasesP.importedFrom] then statement.snaks[aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[aliasesP.inferredFrom] then statement.snaks[aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == aliasesP.author then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- if there are multiple parameter values (authors), add those too for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) else, do some default rendering of name-value pairs, but only if at least "stated in", "reference URL" or "title" is present elseif params[aliasesP.statedIn] or params[aliasesP.referenceURL] or params[aliasesP.title] then citeParams['default'] = {} -- start by adding authors up front if params[aliasesP.author] and #params[aliasesP.author] > 0 then citeParams['default'][#citeParams['default'] + 1] = table.concat(params[aliasesP.author], " & ") end -- combine "reference URL" and "title" into one link if both are present if params[aliasesP.referenceURL] and params[aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '[' .. params[aliasesP.referenceURL][1] .. ' "' .. params[aliasesP.title][1] .. '"]' elseif params[aliasesP.referenceURL] then citeParams['default'][#citeParams['default'] + 1] = params[aliasesP.referenceURL][1] elseif params[aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '"' .. params[aliasesP.title][1] .. '"' end -- then add "stated in" if params[aliasesP.statedIn] then citeParams['default'][#citeParams['default'] + 1] = "''" .. params[aliasesP.statedIn][1] .. "''" end -- remove previously added parameters so that they won't be added a second time params[aliasesP.author] = nil params[aliasesP.referenceURL] = nil params[aliasesP.title] = nil params[aliasesP.statedIn] = nil -- add the rest of the parameters for i, v in pairs(params) do i = self.conf:getLabel(i) if i ~= "" then citeParams['default'][#citeParams['default'] + 1] = i .. ": " .. v[1] end end value = table.concat(citeParams['default'], "; ") if value ~= "" then value = value .. "." end end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p 6tc8vi8zz1ronmbudwfya9yxfw09sa3 803526 803525 2020-04-25T18:23:22Z en>Thayts 0 Minor simplification 803526 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. local p = {} local arg = ... local i18n local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18n = require(title .. "/i18n").init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", publisher = "P123", importedFrom = "P143", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local title = nil local prefix= "" if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id if id:sub(1,1) == "P" then prefix = "Property:" end end prefix = "d:" .. prefix title = label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead title = id prefix = "d:Property:" end end if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://tools.wmflabs.org/geohack/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local version = 1 -- increment this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[aliasesP.importedFrom] then statement.snaks[aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[aliasesP.inferredFrom] then statement.snaks[aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == aliasesP.author then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- if there are multiple parameter values (authors), add those too for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) else, do some default rendering of name-value pairs, but only if at least "stated in", "reference URL" or "title" is present elseif params[aliasesP.statedIn] or params[aliasesP.referenceURL] or params[aliasesP.title] then citeParams['default'] = {} -- start by adding authors up front if params[aliasesP.author] and #params[aliasesP.author] > 0 then citeParams['default'][#citeParams['default'] + 1] = table.concat(params[aliasesP.author], " & ") end -- combine "reference URL" and "title" into one link if both are present if params[aliasesP.referenceURL] and params[aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '[' .. params[aliasesP.referenceURL][1] .. ' "' .. params[aliasesP.title][1] .. '"]' elseif params[aliasesP.referenceURL] then citeParams['default'][#citeParams['default'] + 1] = params[aliasesP.referenceURL][1] elseif params[aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '"' .. params[aliasesP.title][1] .. '"' end -- then add "stated in" if params[aliasesP.statedIn] then citeParams['default'][#citeParams['default'] + 1] = "''" .. params[aliasesP.statedIn][1] .. "''" end -- remove previously added parameters so that they won't be added a second time params[aliasesP.author] = nil params[aliasesP.referenceURL] = nil params[aliasesP.title] = nil params[aliasesP.statedIn] = nil -- add the rest of the parameters for i, v in pairs(params) do i = self.conf:getLabel(i) if i ~= "" then citeParams['default'][#citeParams['default'] + 1] = i .. ": " .. v[1] end end value = table.concat(citeParams['default'], "; ") if value ~= "" then value = value .. "." end end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p d676bdka4cnkgqhgqc73ti65xl5mngq 803527 803526 2020-09-12T00:15:17Z en>IceWelder 0 Avoid Lua errors should Wikidata not be available (tested via sandbox & testcases) 803527 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. local p = {} local arg = ... local i18n local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18n = require(title .. "/i18n").init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", publisher = "P123", importedFrom = "P143", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local title = nil local prefix= "" if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id if id:sub(1,1) == "P" then prefix = "Property:" end end prefix = "d:" .. prefix title = label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead title = id prefix = "d:Property:" end end if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://tools.wmflabs.org/geohack/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local version = 1 -- increment this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[aliasesP.importedFrom] then statement.snaks[aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[aliasesP.inferredFrom] then statement.snaks[aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == aliasesP.author then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- if there are multiple parameter values (authors), add those too for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) else, do some default rendering of name-value pairs, but only if at least "stated in", "reference URL" or "title" is present elseif params[aliasesP.statedIn] or params[aliasesP.referenceURL] or params[aliasesP.title] then citeParams['default'] = {} -- start by adding authors up front if params[aliasesP.author] and #params[aliasesP.author] > 0 then citeParams['default'][#citeParams['default'] + 1] = table.concat(params[aliasesP.author], " & ") end -- combine "reference URL" and "title" into one link if both are present if params[aliasesP.referenceURL] and params[aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '[' .. params[aliasesP.referenceURL][1] .. ' "' .. params[aliasesP.title][1] .. '"]' elseif params[aliasesP.referenceURL] then citeParams['default'][#citeParams['default'] + 1] = params[aliasesP.referenceURL][1] elseif params[aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '"' .. params[aliasesP.title][1] .. '"' end -- then add "stated in" if params[aliasesP.statedIn] then citeParams['default'][#citeParams['default'] + 1] = "''" .. params[aliasesP.statedIn][1] .. "''" end -- remove previously added parameters so that they won't be added a second time params[aliasesP.author] = nil params[aliasesP.referenceURL] = nil params[aliasesP.title] = nil params[aliasesP.statedIn] = nil -- add the rest of the parameters for i, v in pairs(params) do i = self.conf:getLabel(i) if i ~= "" then citeParams['default'][#citeParams['default'] + 1] = i .. ": " .. v[1] end end value = table.concat(citeParams['default'], "; ") if value ~= "" then value = value .. "." end end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p tr2dkpn9ogdr62y68xlj6n0hv4e1vs6 803528 803527 2022-02-18T18:41:24Z en>Uzume 0 escape raw label text pulled from Wikidata, so it cannot be treated as wikitext markup when used in wikitext links 803528 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. local p = {} local arg = ... local i18n local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18n = require(title .. "/i18n").init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", publisher = "P123", importedFrom = "P143", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local title = nil local prefix= "" if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id if id:sub(1,1) == "P" then prefix = "Property:" end end prefix = "d:" .. prefix title = label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead title = id prefix = "d:Property:" end end if title then label = buildWikilink(prefix .. title, mw.text.nowiki(label)) -- escape raw text so it cannot be wikitext markup end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://tools.wmflabs.org/geohack/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local version = 1 -- increment this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[aliasesP.importedFrom] then statement.snaks[aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[aliasesP.inferredFrom] then statement.snaks[aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == aliasesP.author then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- if there are multiple parameter values (authors), add those too for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) else, do some default rendering of name-value pairs, but only if at least "stated in", "reference URL" or "title" is present elseif params[aliasesP.statedIn] or params[aliasesP.referenceURL] or params[aliasesP.title] then citeParams['default'] = {} -- start by adding authors up front if params[aliasesP.author] and #params[aliasesP.author] > 0 then citeParams['default'][#citeParams['default'] + 1] = table.concat(params[aliasesP.author], " & ") end -- combine "reference URL" and "title" into one link if both are present if params[aliasesP.referenceURL] and params[aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '[' .. params[aliasesP.referenceURL][1] .. ' "' .. params[aliasesP.title][1] .. '"]' elseif params[aliasesP.referenceURL] then citeParams['default'][#citeParams['default'] + 1] = params[aliasesP.referenceURL][1] elseif params[aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '"' .. params[aliasesP.title][1] .. '"' end -- then add "stated in" if params[aliasesP.statedIn] then citeParams['default'][#citeParams['default'] + 1] = "''" .. params[aliasesP.statedIn][1] .. "''" end -- remove previously added parameters so that they won't be added a second time params[aliasesP.author] = nil params[aliasesP.referenceURL] = nil params[aliasesP.title] = nil params[aliasesP.statedIn] = nil -- add the rest of the parameters for i, v in pairs(params) do i = self.conf:getLabel(i) if i ~= "" then citeParams['default'][#citeParams['default'] + 1] = i .. ": " .. v[1] end end value = table.concat(citeParams['default'], "; ") if value ~= "" then value = value .. "." end end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p 52q9z6uhox53miuowkafz4s2obgkybr 803529 803528 2022-02-18T19:15:39Z en>Uzume 0 always return an wikilink for Wikidata items when requested; even when there is no applicable sitelink available: link to Wikidata 803529 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. local p = {} local arg = ... local i18n local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18n = require(title .. "/i18n").init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", publisher = "P123", importedFrom = "P143", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local title = nil local prefix= "" if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id if id:sub(1,1) == "P" then prefix = "Property:" end end prefix = "d:" .. prefix title = label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) if not title then title = id prefix = "d:Special:EntityPage/" end elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead title = id prefix = "d:Special:EntityPage/" end end if title then label = buildWikilink(prefix .. title, mw.text.nowiki(label)) -- escape raw text so it cannot be wikitext markup end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://tools.wmflabs.org/geohack/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local version = 1 -- increment this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[aliasesP.importedFrom] then statement.snaks[aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[aliasesP.inferredFrom] then statement.snaks[aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == aliasesP.author then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- if there are multiple parameter values (authors), add those too for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) else, do some default rendering of name-value pairs, but only if at least "stated in", "reference URL" or "title" is present elseif params[aliasesP.statedIn] or params[aliasesP.referenceURL] or params[aliasesP.title] then citeParams['default'] = {} -- start by adding authors up front if params[aliasesP.author] and #params[aliasesP.author] > 0 then citeParams['default'][#citeParams['default'] + 1] = table.concat(params[aliasesP.author], " & ") end -- combine "reference URL" and "title" into one link if both are present if params[aliasesP.referenceURL] and params[aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '[' .. params[aliasesP.referenceURL][1] .. ' "' .. params[aliasesP.title][1] .. '"]' elseif params[aliasesP.referenceURL] then citeParams['default'][#citeParams['default'] + 1] = params[aliasesP.referenceURL][1] elseif params[aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '"' .. params[aliasesP.title][1] .. '"' end -- then add "stated in" if params[aliasesP.statedIn] then citeParams['default'][#citeParams['default'] + 1] = "''" .. params[aliasesP.statedIn][1] .. "''" end -- remove previously added parameters so that they won't be added a second time params[aliasesP.author] = nil params[aliasesP.referenceURL] = nil params[aliasesP.title] = nil params[aliasesP.statedIn] = nil -- add the rest of the parameters for i, v in pairs(params) do i = self.conf:getLabel(i) if i ~= "" then citeParams['default'][#citeParams['default'] + 1] = i .. ": " .. v[1] end end value = table.concat(citeParams['default'], "; ") if value ~= "" then value = value .. "." end end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p 82rz8jow90gl55vst4p18vx8owt8spq 803530 803529 2022-02-21T22:33:11Z en>Uzume 0 cmd label|linked: stop linking to Wikidata items when there is no local sitelink (as per [[Module talk:Wd#Linking non-notable people to Wikidata entry]]); also allow raw links to any Wikidata entity (e.g., including lexeme which do not have multilingual labels like items and properties have) 803530 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. local p = {} local arg = ... local i18n local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18n = require(title .. "/i18n").init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", publisher = "P123", importedFrom = "P143", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local title = nil local prefix= "" if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id end prefix, title = "d:Special:EntityPage/", label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then if mw.wikibase.getSitelink(id) then prefix, title = "d:Special:GoToLinkedPage/"..cfg.siteID.."/", id else --[=[ no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id]=] end elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id end end if title then label = buildWikilink(prefix .. title, mw.text.nowiki(label)) -- escape raw text so it cannot be wikitext markup end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://tools.wmflabs.org/geohack/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local version = 1 -- increment this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[aliasesP.importedFrom] then statement.snaks[aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[aliasesP.inferredFrom] then statement.snaks[aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == aliasesP.author then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- if there are multiple parameter values (authors), add those too for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) else, do some default rendering of name-value pairs, but only if at least "stated in", "reference URL" or "title" is present elseif params[aliasesP.statedIn] or params[aliasesP.referenceURL] or params[aliasesP.title] then citeParams['default'] = {} -- start by adding authors up front if params[aliasesP.author] and #params[aliasesP.author] > 0 then citeParams['default'][#citeParams['default'] + 1] = table.concat(params[aliasesP.author], " & ") end -- combine "reference URL" and "title" into one link if both are present if params[aliasesP.referenceURL] and params[aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '[' .. params[aliasesP.referenceURL][1] .. ' "' .. params[aliasesP.title][1] .. '"]' elseif params[aliasesP.referenceURL] then citeParams['default'][#citeParams['default'] + 1] = params[aliasesP.referenceURL][1] elseif params[aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '"' .. params[aliasesP.title][1] .. '"' end -- then add "stated in" if params[aliasesP.statedIn] then citeParams['default'][#citeParams['default'] + 1] = "''" .. params[aliasesP.statedIn][1] .. "''" end -- remove previously added parameters so that they won't be added a second time params[aliasesP.author] = nil params[aliasesP.referenceURL] = nil params[aliasesP.title] = nil params[aliasesP.statedIn] = nil -- add the rest of the parameters for i, v in pairs(params) do i = self.conf:getLabel(i) if i ~= "" then citeParams['default'][#citeParams['default'] + 1] = i .. ": " .. v[1] end end value = table.concat(citeParams['default'], "; ") if value ~= "" then value = value .. "." end end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p b2whedqtye60hkzks5g4y6yii7sf7wg 803531 803530 2022-02-21T22:35:04Z en>Uzume 0 fix 803531 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. local p = {} local arg = ... local i18n local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18n = require(title .. "/i18n").init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", publisher = "P123", importedFrom = "P143", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local title = nil local prefix= "" if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id end prefix, title = "d:Special:EntityPage/", label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then if mw.wikibase.getSitelink(id) then prefix, title = "d:Special:GoToLinkedPage/"..mw.wikibase.getGlobalSiteId().."/", id else --[=[ no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id]=] end elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id end end if title then label = buildWikilink(prefix .. title, mw.text.nowiki(label)) -- escape raw text so it cannot be wikitext markup end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://tools.wmflabs.org/geohack/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local version = 1 -- increment this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[aliasesP.importedFrom] then statement.snaks[aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[aliasesP.inferredFrom] then statement.snaks[aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == aliasesP.author then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- if there are multiple parameter values (authors), add those too for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) else, do some default rendering of name-value pairs, but only if at least "stated in", "reference URL" or "title" is present elseif params[aliasesP.statedIn] or params[aliasesP.referenceURL] or params[aliasesP.title] then citeParams['default'] = {} -- start by adding authors up front if params[aliasesP.author] and #params[aliasesP.author] > 0 then citeParams['default'][#citeParams['default'] + 1] = table.concat(params[aliasesP.author], " & ") end -- combine "reference URL" and "title" into one link if both are present if params[aliasesP.referenceURL] and params[aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '[' .. params[aliasesP.referenceURL][1] .. ' "' .. params[aliasesP.title][1] .. '"]' elseif params[aliasesP.referenceURL] then citeParams['default'][#citeParams['default'] + 1] = params[aliasesP.referenceURL][1] elseif params[aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '"' .. params[aliasesP.title][1] .. '"' end -- then add "stated in" if params[aliasesP.statedIn] then citeParams['default'][#citeParams['default'] + 1] = "''" .. params[aliasesP.statedIn][1] .. "''" end -- remove previously added parameters so that they won't be added a second time params[aliasesP.author] = nil params[aliasesP.referenceURL] = nil params[aliasesP.title] = nil params[aliasesP.statedIn] = nil -- add the rest of the parameters for i, v in pairs(params) do i = self.conf:getLabel(i) if i ~= "" then citeParams['default'][#citeParams['default'] + 1] = i .. ": " .. v[1] end end value = table.concat(citeParams['default'], "; ") if value ~= "" then value = value .. "." end end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p m1878bsf7qqtcok3l4og768jw8b3st4 803532 803531 2022-02-22T20:58:50Z en>Uzume 0 always escape raw label from Wikidata when link is requested—even when there is no local sitelink and a wikitext link is not returned; raw label is still returned when link is not requested 803532 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. local p = {} local arg = ... local i18n local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18n = require(title .. "/i18n").init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", publisher = "P123", importedFrom = "P143", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local prefix, title= "", nil if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id end prefix, title = "d:Special:EntityPage/", label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) -- XXX: should use fallback labels? end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then if mw.wikibase.getSitelink(id) then prefix, title = "d:Special:GoToLinkedPage/"..self.siteID.."/", id else --[=[ no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id]=] end elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id end end label = mw.text.nowiki(label) -- escape raw label text so it cannot be wikitext markup if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://tools.wmflabs.org/geohack/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local version = 1 -- increment this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[aliasesP.importedFrom] then statement.snaks[aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[aliasesP.inferredFrom] then statement.snaks[aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == aliasesP.author then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- if there are multiple parameter values (authors), add those too for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) else, do some default rendering of name-value pairs, but only if at least "stated in", "reference URL" or "title" is present elseif params[aliasesP.statedIn] or params[aliasesP.referenceURL] or params[aliasesP.title] then citeParams['default'] = {} -- start by adding authors up front if params[aliasesP.author] and #params[aliasesP.author] > 0 then citeParams['default'][#citeParams['default'] + 1] = table.concat(params[aliasesP.author], " & ") end -- combine "reference URL" and "title" into one link if both are present if params[aliasesP.referenceURL] and params[aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '[' .. params[aliasesP.referenceURL][1] .. ' "' .. params[aliasesP.title][1] .. '"]' elseif params[aliasesP.referenceURL] then citeParams['default'][#citeParams['default'] + 1] = params[aliasesP.referenceURL][1] elseif params[aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '"' .. params[aliasesP.title][1] .. '"' end -- then add "stated in" if params[aliasesP.statedIn] then citeParams['default'][#citeParams['default'] + 1] = "''" .. params[aliasesP.statedIn][1] .. "''" end -- remove previously added parameters so that they won't be added a second time params[aliasesP.author] = nil params[aliasesP.referenceURL] = nil params[aliasesP.title] = nil params[aliasesP.statedIn] = nil -- add the rest of the parameters for i, v in pairs(params) do i = self.conf:getLabel(i) if i ~= "" then citeParams['default'][#citeParams['default'] + 1] = i .. ": " .. v[1] end end value = table.concat(citeParams['default'], "; ") if value ~= "" then value = value .. "." end end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p 976p68fkvzv3x07i255x91si2id25w9 803533 803532 2022-02-23T17:08:07Z en>Thayts 0 Partial revert to undo introduction of linking obscurity 803533 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. local p = {} local arg = ... local i18n local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18n = require(title .. "/i18n").init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", publisher = "P123", importedFrom = "P143", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local prefix, title= "", nil if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id end prefix, title = "d:Special:EntityPage/", label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) -- XXX: should use fallback labels? end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id end end label = mw.text.nowiki(label) -- escape raw label text so it cannot be wikitext markup if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://tools.wmflabs.org/geohack/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local version = 1 -- increment this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[aliasesP.importedFrom] then statement.snaks[aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[aliasesP.inferredFrom] then statement.snaks[aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == aliasesP.author then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- if there are multiple parameter values (authors), add those too for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) else, do some default rendering of name-value pairs, but only if at least "stated in", "reference URL" or "title" is present elseif params[aliasesP.statedIn] or params[aliasesP.referenceURL] or params[aliasesP.title] then citeParams['default'] = {} -- start by adding authors up front if params[aliasesP.author] and #params[aliasesP.author] > 0 then citeParams['default'][#citeParams['default'] + 1] = table.concat(params[aliasesP.author], " & ") end -- combine "reference URL" and "title" into one link if both are present if params[aliasesP.referenceURL] and params[aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '[' .. params[aliasesP.referenceURL][1] .. ' "' .. params[aliasesP.title][1] .. '"]' elseif params[aliasesP.referenceURL] then citeParams['default'][#citeParams['default'] + 1] = params[aliasesP.referenceURL][1] elseif params[aliasesP.title] then citeParams['default'][#citeParams['default'] + 1] = '"' .. params[aliasesP.title][1] .. '"' end -- then add "stated in" if params[aliasesP.statedIn] then citeParams['default'][#citeParams['default'] + 1] = "''" .. params[aliasesP.statedIn][1] .. "''" end -- remove previously added parameters so that they won't be added a second time params[aliasesP.author] = nil params[aliasesP.referenceURL] = nil params[aliasesP.title] = nil params[aliasesP.statedIn] = nil -- add the rest of the parameters for i, v in pairs(params) do i = self.conf:getLabel(i) if i ~= "" then citeParams['default'][#citeParams['default'] + 1] = i .. ": " .. v[1] end end value = table.concat(citeParams['default'], "; ") if value ~= "" then value = value .. "." end end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p 9scru16s5146chaxiyatugrhsfa43zn 803534 803533 2023-05-06T16:54:09Z en>Pppery 0 Per edit request 803534 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. local p = {} local arg = ... local i18n local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18n = require(title .. "/i18n").init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", authorNameString = "P2093", publisher = "P123", importedFrom = "P143", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local prefix, title= "", nil if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id end prefix, title = "d:Special:EntityPage/", label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) -- XXX: should use fallback labels? end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id end end label = mw.text.nowiki(label) -- escape raw label text so it cannot be wikitext markup if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://tools.wmflabs.org/geohack/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local referenceEmpty = true -- will be set to false if at least one parameter is left unremoved local numAuthorParameters = 0 local numAuthorNameStringParameters = 0 local version = 2 -- increment this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[aliasesP.importedFrom] then statement.snaks[aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[aliasesP.inferredFrom] then statement.snaks[aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == aliasesP.author or i == aliasesP.authorNameString then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else referenceEmpty = false if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then -- handle non-author properties (and author properties ("author" and "author name string"), if they don't use the same template parameter) if (i ~= aliasesP.author and i ~= aliasesP.authorNameString) or (i18n['cite'][j][aliasesP.author] ~= i18n['cite'][j][aliasesP.authorNameString]) then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- to avoid problems with non-author multiple parameters (if existent), the following old code is retained for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end -- handle "author" and "author name string" specially if they use the same template parameter elseif i == aliasesP.author or i == aliasesP.authorNameString then if params[aliasesP.author] ~= nil then numAuthorParameters = #params[aliasesP.author] else numAuthorParameters = 0 end if params[aliasesP.authorNameString] ~= nil then numAuthorNameStringParameters = #params[aliasesP.authorNameString] else numAuthorNameStringParameters = 0 end -- execute only if both "author" and "author name string" satisfy this condition: the property is both in params and in statement.snaks or it is neither in params nor in statement.snaks -- reason: parameters are added to params each iteration of the loop, not before the loop if ((statement.snaks[aliasesP.author] == nil) == (numAuthorParameters == 0)) and ((statement.snaks[aliasesP.authorNameString] == nil) == (numAuthorNameStringParameters == 0)) then for k=1, numAuthorParameters + numAuthorNameStringParameters do if k <= numAuthorParameters then -- now handling the authors from the "author" property citeParams[j][i18n['cite'][j][aliasesP.author]..k] = label .. params[aliasesP.author][k] else -- now handling the authors from "author name string" citeParams[j][i18n['cite'][j][aliasesP.authorNameString]..k] = label .. params[aliasesP.authorNameString][k - numAuthorParameters] end end end end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) if the citation couldn't be displayed using Cite web or Cite Q, but has properties other than the removed ones, throw an error elseif not referenceEmpty then value = "<span style=\"color: crimson\">" .. errorText("malformed-reference") .. "</span>" end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p 62oz0m09z2nkf3lw8vq0i8sco64lmgw 803535 756519 2023-06-15T20:42:15Z en>Uzume 0 import [[voy:Special:Diff/4022511/prev]] tools.wmflabs.org/geohack -> geohack.toolforge.org 803535 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. local p = {} local arg = ... local i18n local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18n = require(title .. "/i18n").init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", authorNameString = "P2093", publisher = "P123", importedFrom = "P143", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local prefix, title= "", nil if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id end prefix, title = "d:Special:EntityPage/", label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) -- XXX: should use fallback labels? end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id end end label = mw.text.nowiki(label) -- escape raw label text so it cannot be wikitext markup if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://geohack.toolforge.org/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local referenceEmpty = true -- will be set to false if at least one parameter is left unremoved local numAuthorParameters = 0 local numAuthorNameStringParameters = 0 local version = 2 -- increment this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[aliasesP.importedFrom] then statement.snaks[aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[aliasesP.inferredFrom] then statement.snaks[aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == aliasesP.author or i == aliasesP.authorNameString then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else referenceEmpty = false if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then -- handle non-author properties (and author properties ("author" and "author name string"), if they don't use the same template parameter) if (i ~= aliasesP.author and i ~= aliasesP.authorNameString) or (i18n['cite'][j][aliasesP.author] ~= i18n['cite'][j][aliasesP.authorNameString]) then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- to avoid problems with non-author multiple parameters (if existent), the following old code is retained for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end -- handle "author" and "author name string" specially if they use the same template parameter elseif i == aliasesP.author or i == aliasesP.authorNameString then if params[aliasesP.author] ~= nil then numAuthorParameters = #params[aliasesP.author] else numAuthorParameters = 0 end if params[aliasesP.authorNameString] ~= nil then numAuthorNameStringParameters = #params[aliasesP.authorNameString] else numAuthorNameStringParameters = 0 end -- execute only if both "author" and "author name string" satisfy this condition: the property is both in params and in statement.snaks or it is neither in params nor in statement.snaks -- reason: parameters are added to params each iteration of the loop, not before the loop if ((statement.snaks[aliasesP.author] == nil) == (numAuthorParameters == 0)) and ((statement.snaks[aliasesP.authorNameString] == nil) == (numAuthorNameStringParameters == 0)) then for k=1, numAuthorParameters + numAuthorNameStringParameters do if k <= numAuthorParameters then -- now handling the authors from the "author" property citeParams[j][i18n['cite'][j][aliasesP.author]..k] = label .. params[aliasesP.author][k] else -- now handling the authors from "author name string" citeParams[j][i18n['cite'][j][aliasesP.authorNameString]..k] = label .. params[aliasesP.authorNameString][k - numAuthorParameters] end end end end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) if the citation couldn't be displayed using Cite web or Cite Q, but has properties other than the removed ones, throw an error elseif not referenceEmpty then value = "<span style=\"color: crimson\">" .. errorText("malformed-reference") .. "</span>" end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p 19mwvad9pmi1jhxk6mxi8djpqnzqt9i 803536 803535 2023-06-15T21:00:50Z en>Uzume 0 localize "copyTable" 803536 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. local p = {} local arg = ... local i18n local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18n = require(title .. "/i18n").init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", authorNameString = "P2093", publisher = "P123", importedFrom = "P143", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables local function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local prefix, title= "", nil if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id end prefix, title = "d:Special:EntityPage/", label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) -- XXX: should use fallback labels? end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id end end label = mw.text.nowiki(label) -- escape raw label text so it cannot be wikitext markup if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://geohack.toolforge.org/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local referenceEmpty = true -- will be set to false if at least one parameter is left unremoved local numAuthorParameters = 0 local numAuthorNameStringParameters = 0 local version = 2 -- increment this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[aliasesP.importedFrom] then statement.snaks[aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[aliasesP.inferredFrom] then statement.snaks[aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == aliasesP.author or i == aliasesP.authorNameString then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else referenceEmpty = false if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then -- handle non-author properties (and author properties ("author" and "author name string"), if they don't use the same template parameter) if (i ~= aliasesP.author and i ~= aliasesP.authorNameString) or (i18n['cite'][j][aliasesP.author] ~= i18n['cite'][j][aliasesP.authorNameString]) then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- to avoid problems with non-author multiple parameters (if existent), the following old code is retained for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end -- handle "author" and "author name string" specially if they use the same template parameter elseif i == aliasesP.author or i == aliasesP.authorNameString then if params[aliasesP.author] ~= nil then numAuthorParameters = #params[aliasesP.author] else numAuthorParameters = 0 end if params[aliasesP.authorNameString] ~= nil then numAuthorNameStringParameters = #params[aliasesP.authorNameString] else numAuthorNameStringParameters = 0 end -- execute only if both "author" and "author name string" satisfy this condition: the property is both in params and in statement.snaks or it is neither in params nor in statement.snaks -- reason: parameters are added to params each iteration of the loop, not before the loop if ((statement.snaks[aliasesP.author] == nil) == (numAuthorParameters == 0)) and ((statement.snaks[aliasesP.authorNameString] == nil) == (numAuthorNameStringParameters == 0)) then for k=1, numAuthorParameters + numAuthorNameStringParameters do if k <= numAuthorParameters then -- now handling the authors from the "author" property citeParams[j][i18n['cite'][j][aliasesP.author]..k] = label .. params[aliasesP.author][k] else -- now handling the authors from "author name string" citeParams[j][i18n['cite'][j][aliasesP.authorNameString]..k] = label .. params[aliasesP.authorNameString][k - numAuthorParameters] end end end end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) if the citation couldn't be displayed using Cite web or Cite Q, but has properties other than the removed ones, throw an error elseif not referenceEmpty then value = "<span style=\"color: crimson\">" .. errorText("malformed-reference") .. "</span>" end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p qi54p6abr2d5xohpgnpwxd4yqc5da7z 803537 803536 2023-06-15T21:01:37Z en>Uzume 0 Undid revision 1160335980 by [[Special:Contributions/Uzume|Uzume]] ([[User talk:Uzume|talk]]) 803537 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. local p = {} local arg = ... local i18n local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18n = require(title .. "/i18n").init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", authorNameString = "P2093", publisher = "P123", importedFrom = "P143", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local prefix, title= "", nil if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id end prefix, title = "d:Special:EntityPage/", label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) -- XXX: should use fallback labels? end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id end end label = mw.text.nowiki(label) -- escape raw label text so it cannot be wikitext markup if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://geohack.toolforge.org/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local referenceEmpty = true -- will be set to false if at least one parameter is left unremoved local numAuthorParameters = 0 local numAuthorNameStringParameters = 0 local version = 2 -- increment this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[aliasesP.importedFrom] then statement.snaks[aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[aliasesP.inferredFrom] then statement.snaks[aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == aliasesP.author or i == aliasesP.authorNameString then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else referenceEmpty = false if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then -- handle non-author properties (and author properties ("author" and "author name string"), if they don't use the same template parameter) if (i ~= aliasesP.author and i ~= aliasesP.authorNameString) or (i18n['cite'][j][aliasesP.author] ~= i18n['cite'][j][aliasesP.authorNameString]) then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- to avoid problems with non-author multiple parameters (if existent), the following old code is retained for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end -- handle "author" and "author name string" specially if they use the same template parameter elseif i == aliasesP.author or i == aliasesP.authorNameString then if params[aliasesP.author] ~= nil then numAuthorParameters = #params[aliasesP.author] else numAuthorParameters = 0 end if params[aliasesP.authorNameString] ~= nil then numAuthorNameStringParameters = #params[aliasesP.authorNameString] else numAuthorNameStringParameters = 0 end -- execute only if both "author" and "author name string" satisfy this condition: the property is both in params and in statement.snaks or it is neither in params nor in statement.snaks -- reason: parameters are added to params each iteration of the loop, not before the loop if ((statement.snaks[aliasesP.author] == nil) == (numAuthorParameters == 0)) and ((statement.snaks[aliasesP.authorNameString] == nil) == (numAuthorNameStringParameters == 0)) then for k=1, numAuthorParameters + numAuthorNameStringParameters do if k <= numAuthorParameters then -- now handling the authors from the "author" property citeParams[j][i18n['cite'][j][aliasesP.author]..k] = label .. params[aliasesP.author][k] else -- now handling the authors from "author name string" citeParams[j][i18n['cite'][j][aliasesP.authorNameString]..k] = label .. params[aliasesP.authorNameString][k - numAuthorParameters] end end end end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) if the citation couldn't be displayed using Cite web or Cite Q, but has properties other than the removed ones, throw an error elseif not referenceEmpty then value = "<span style=\"color: crimson\">" .. errorText("malformed-reference") .. "</span>" end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p 19mwvad9pmi1jhxk6mxi8djpqnzqt9i 803538 803537 2023-06-15T21:02:24Z en>Uzume 0 Undid revision 1160336072 by [[Special:Contributions/Uzume|Uzume]] ([[User talk:Uzume|talk]]) 803538 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. local p = {} local arg = ... local i18n local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18n = require(title .. "/i18n").init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", authorNameString = "P2093", publisher = "P123", importedFrom = "P143", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables local function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local prefix, title= "", nil if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id end prefix, title = "d:Special:EntityPage/", label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) -- XXX: should use fallback labels? end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id end end label = mw.text.nowiki(label) -- escape raw label text so it cannot be wikitext markup if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://geohack.toolforge.org/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local referenceEmpty = true -- will be set to false if at least one parameter is left unremoved local numAuthorParameters = 0 local numAuthorNameStringParameters = 0 local version = 2 -- increment this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[aliasesP.importedFrom] then statement.snaks[aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[aliasesP.inferredFrom] then statement.snaks[aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == aliasesP.author or i == aliasesP.authorNameString then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else referenceEmpty = false if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then -- handle non-author properties (and author properties ("author" and "author name string"), if they don't use the same template parameter) if (i ~= aliasesP.author and i ~= aliasesP.authorNameString) or (i18n['cite'][j][aliasesP.author] ~= i18n['cite'][j][aliasesP.authorNameString]) then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- to avoid problems with non-author multiple parameters (if existent), the following old code is retained for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end -- handle "author" and "author name string" specially if they use the same template parameter elseif i == aliasesP.author or i == aliasesP.authorNameString then if params[aliasesP.author] ~= nil then numAuthorParameters = #params[aliasesP.author] else numAuthorParameters = 0 end if params[aliasesP.authorNameString] ~= nil then numAuthorNameStringParameters = #params[aliasesP.authorNameString] else numAuthorNameStringParameters = 0 end -- execute only if both "author" and "author name string" satisfy this condition: the property is both in params and in statement.snaks or it is neither in params nor in statement.snaks -- reason: parameters are added to params each iteration of the loop, not before the loop if ((statement.snaks[aliasesP.author] == nil) == (numAuthorParameters == 0)) and ((statement.snaks[aliasesP.authorNameString] == nil) == (numAuthorNameStringParameters == 0)) then for k=1, numAuthorParameters + numAuthorNameStringParameters do if k <= numAuthorParameters then -- now handling the authors from the "author" property citeParams[j][i18n['cite'][j][aliasesP.author]..k] = label .. params[aliasesP.author][k] else -- now handling the authors from "author name string" citeParams[j][i18n['cite'][j][aliasesP.authorNameString]..k] = label .. params[aliasesP.authorNameString][k - numAuthorParameters] end end end end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) if the citation couldn't be displayed using Cite web or Cite Q, but has properties other than the removed ones, throw an error elseif not referenceEmpty then value = "<span style=\"color: crimson\">" .. errorText("malformed-reference") .. "</span>" end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p qi54p6abr2d5xohpgnpwxd4yqc5da7z 803539 803538 2023-06-15T21:05:13Z en>Uzume 0 fix localization 803539 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. local p = {} local arg = ... local i18n local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18n = require(title .. "/i18n").init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", authorNameString = "P2093", publisher = "P123", importedFrom = "P143", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local copyTable local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local prefix, title= "", nil if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id end prefix, title = "d:Special:EntityPage/", label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) -- XXX: should use fallback labels? end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id end end label = mw.text.nowiki(label) -- escape raw label text so it cannot be wikitext markup if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://geohack.toolforge.org/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local referenceEmpty = true -- will be set to false if at least one parameter is left unremoved local numAuthorParameters = 0 local numAuthorNameStringParameters = 0 local version = 2 -- increment this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[aliasesP.importedFrom] then statement.snaks[aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[aliasesP.inferredFrom] then statement.snaks[aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == aliasesP.author or i == aliasesP.authorNameString then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else referenceEmpty = false if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then -- handle non-author properties (and author properties ("author" and "author name string"), if they don't use the same template parameter) if (i ~= aliasesP.author and i ~= aliasesP.authorNameString) or (i18n['cite'][j][aliasesP.author] ~= i18n['cite'][j][aliasesP.authorNameString]) then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- to avoid problems with non-author multiple parameters (if existent), the following old code is retained for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end -- handle "author" and "author name string" specially if they use the same template parameter elseif i == aliasesP.author or i == aliasesP.authorNameString then if params[aliasesP.author] ~= nil then numAuthorParameters = #params[aliasesP.author] else numAuthorParameters = 0 end if params[aliasesP.authorNameString] ~= nil then numAuthorNameStringParameters = #params[aliasesP.authorNameString] else numAuthorNameStringParameters = 0 end -- execute only if both "author" and "author name string" satisfy this condition: the property is both in params and in statement.snaks or it is neither in params nor in statement.snaks -- reason: parameters are added to params each iteration of the loop, not before the loop if ((statement.snaks[aliasesP.author] == nil) == (numAuthorParameters == 0)) and ((statement.snaks[aliasesP.authorNameString] == nil) == (numAuthorNameStringParameters == 0)) then for k=1, numAuthorParameters + numAuthorNameStringParameters do if k <= numAuthorParameters then -- now handling the authors from the "author" property citeParams[j][i18n['cite'][j][aliasesP.author]..k] = label .. params[aliasesP.author][k] else -- now handling the authors from "author name string" citeParams[j][i18n['cite'][j][aliasesP.authorNameString]..k] = label .. params[aliasesP.authorNameString][k - numAuthorParameters] end end end end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) if the citation couldn't be displayed using Cite web or Cite Q, but has properties other than the removed ones, throw an error elseif not referenceEmpty then value = "<span style=\"color: crimson\">" .. errorText("malformed-reference") .. "</span>" end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p b90xwzph43y66ax840m91vyrd58qo5c 803540 803539 2023-06-15T21:09:49Z en>Uzume 0 localize "parseDate" 803540 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. local p = {} local arg = ... local i18n local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18n = require(title .. "/i18n").init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", authorNameString = "P2093", publisher = "P123", importedFrom = "P143", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local copyTable, parseDate local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local prefix, title= "", nil if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id end prefix, title = "d:Special:EntityPage/", label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) -- XXX: should use fallback labels? end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id end end label = mw.text.nowiki(label) -- escape raw label text so it cannot be wikitext markup if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://geohack.toolforge.org/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local referenceEmpty = true -- will be set to false if at least one parameter is left unremoved local numAuthorParameters = 0 local numAuthorNameStringParameters = 0 local version = 2 -- increment this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[aliasesP.importedFrom] then statement.snaks[aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[aliasesP.inferredFrom] then statement.snaks[aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == aliasesP.author or i == aliasesP.authorNameString then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else referenceEmpty = false if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then -- handle non-author properties (and author properties ("author" and "author name string"), if they don't use the same template parameter) if (i ~= aliasesP.author and i ~= aliasesP.authorNameString) or (i18n['cite'][j][aliasesP.author] ~= i18n['cite'][j][aliasesP.authorNameString]) then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- to avoid problems with non-author multiple parameters (if existent), the following old code is retained for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end -- handle "author" and "author name string" specially if they use the same template parameter elseif i == aliasesP.author or i == aliasesP.authorNameString then if params[aliasesP.author] ~= nil then numAuthorParameters = #params[aliasesP.author] else numAuthorParameters = 0 end if params[aliasesP.authorNameString] ~= nil then numAuthorNameStringParameters = #params[aliasesP.authorNameString] else numAuthorNameStringParameters = 0 end -- execute only if both "author" and "author name string" satisfy this condition: the property is both in params and in statement.snaks or it is neither in params nor in statement.snaks -- reason: parameters are added to params each iteration of the loop, not before the loop if ((statement.snaks[aliasesP.author] == nil) == (numAuthorParameters == 0)) and ((statement.snaks[aliasesP.authorNameString] == nil) == (numAuthorNameStringParameters == 0)) then for k=1, numAuthorParameters + numAuthorNameStringParameters do if k <= numAuthorParameters then -- now handling the authors from the "author" property citeParams[j][i18n['cite'][j][aliasesP.author]..k] = label .. params[aliasesP.author][k] else -- now handling the authors from "author name string" citeParams[j][i18n['cite'][j][aliasesP.authorNameString]..k] = label .. params[aliasesP.authorNameString][k - numAuthorParameters] end end end end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) if the citation couldn't be displayed using Cite web or Cite Q, but has properties other than the removed ones, throw an error elseif not referenceEmpty then value = "<span style=\"color: crimson\">" .. errorText("malformed-reference") .. "</span>" end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p bvcqtiyo2u0mbs3q06ygw9jn26d8iih 803541 803540 2023-06-15T21:14:07Z en>Uzume 0 strict 803541 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. require("strict") local p = {} local arg = ... local i18n local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18n = require(title .. "/i18n").init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", authorNameString = "P2093", publisher = "P123", importedFrom = "P143", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local copyTable, parseDate -- forward declarations local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local prefix, title= "", nil if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id end prefix, title = "d:Special:EntityPage/", label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) -- XXX: should use fallback labels? end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id end end label = mw.text.nowiki(label) -- escape raw label text so it cannot be wikitext markup if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://geohack.toolforge.org/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local referenceEmpty = true -- will be set to false if at least one parameter is left unremoved local numAuthorParameters = 0 local numAuthorNameStringParameters = 0 local version = 2 -- increment this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[aliasesP.importedFrom] then statement.snaks[aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[aliasesP.inferredFrom] then statement.snaks[aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == aliasesP.author or i == aliasesP.authorNameString then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else referenceEmpty = false if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then -- handle non-author properties (and author properties ("author" and "author name string"), if they don't use the same template parameter) if (i ~= aliasesP.author and i ~= aliasesP.authorNameString) or (i18n['cite'][j][aliasesP.author] ~= i18n['cite'][j][aliasesP.authorNameString]) then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- to avoid problems with non-author multiple parameters (if existent), the following old code is retained for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end -- handle "author" and "author name string" specially if they use the same template parameter elseif i == aliasesP.author or i == aliasesP.authorNameString then if params[aliasesP.author] ~= nil then numAuthorParameters = #params[aliasesP.author] else numAuthorParameters = 0 end if params[aliasesP.authorNameString] ~= nil then numAuthorNameStringParameters = #params[aliasesP.authorNameString] else numAuthorNameStringParameters = 0 end -- execute only if both "author" and "author name string" satisfy this condition: the property is both in params and in statement.snaks or it is neither in params nor in statement.snaks -- reason: parameters are added to params each iteration of the loop, not before the loop if ((statement.snaks[aliasesP.author] == nil) == (numAuthorParameters == 0)) and ((statement.snaks[aliasesP.authorNameString] == nil) == (numAuthorNameStringParameters == 0)) then for k=1, numAuthorParameters + numAuthorNameStringParameters do if k <= numAuthorParameters then -- now handling the authors from the "author" property citeParams[j][i18n['cite'][j][aliasesP.author]..k] = label .. params[aliasesP.author][k] else -- now handling the authors from "author name string" citeParams[j][i18n['cite'][j][aliasesP.authorNameString]..k] = label .. params[aliasesP.authorNameString][k - numAuthorParameters] end end end end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) if the citation couldn't be displayed using Cite web or Cite Q, but has properties other than the removed ones, throw an error elseif not referenceEmpty then value = "<span style=\"color: crimson\">" .. errorText("malformed-reference") .. "</span>" end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p itbdurbcp43v78pdi053pmexne9l5dj 803542 756520 2023-06-16T02:55:10Z en>Uzume 0 move definition of module locals before their usage 803542 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. require("strict") local p = {} local arg = ... local i18n local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18n = require(title .. "/i18n").init(aliasesP) end end local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables local function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end local function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", authorNameString = "P2093", publisher = "P123", importedFrom = "P143", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local prefix, title= "", nil if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id end prefix, title = "d:Special:EntityPage/", label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) -- XXX: should use fallback labels? end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id end end label = mw.text.nowiki(label) -- escape raw label text so it cannot be wikitext markup if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://geohack.toolforge.org/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local referenceEmpty = true -- will be set to false if at least one parameter is left unremoved local numAuthorParameters = 0 local numAuthorNameStringParameters = 0 local version = 2 -- increment this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[aliasesP.importedFrom] then statement.snaks[aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[aliasesP.inferredFrom] then statement.snaks[aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == aliasesP.author or i == aliasesP.authorNameString then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else referenceEmpty = false if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then -- handle non-author properties (and author properties ("author" and "author name string"), if they don't use the same template parameter) if (i ~= aliasesP.author and i ~= aliasesP.authorNameString) or (i18n['cite'][j][aliasesP.author] ~= i18n['cite'][j][aliasesP.authorNameString]) then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- to avoid problems with non-author multiple parameters (if existent), the following old code is retained for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end -- handle "author" and "author name string" specially if they use the same template parameter elseif i == aliasesP.author or i == aliasesP.authorNameString then if params[aliasesP.author] ~= nil then numAuthorParameters = #params[aliasesP.author] else numAuthorParameters = 0 end if params[aliasesP.authorNameString] ~= nil then numAuthorNameStringParameters = #params[aliasesP.authorNameString] else numAuthorNameStringParameters = 0 end -- execute only if both "author" and "author name string" satisfy this condition: the property is both in params and in statement.snaks or it is neither in params nor in statement.snaks -- reason: parameters are added to params each iteration of the loop, not before the loop if ((statement.snaks[aliasesP.author] == nil) == (numAuthorParameters == 0)) and ((statement.snaks[aliasesP.authorNameString] == nil) == (numAuthorNameStringParameters == 0)) then for k=1, numAuthorParameters + numAuthorNameStringParameters do if k <= numAuthorParameters then -- now handling the authors from the "author" property citeParams[j][i18n['cite'][j][aliasesP.author]..k] = label .. params[aliasesP.author][k] else -- now handling the authors from "author name string" citeParams[j][i18n['cite'][j][aliasesP.authorNameString]..k] = label .. params[aliasesP.authorNameString][k - numAuthorParameters] end end end end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) if the citation couldn't be displayed using Cite web or Cite Q, but has properties other than the removed ones, throw an error elseif not referenceEmpty then value = "<span style=\"color: crimson\">" .. errorText("malformed-reference") .. "</span>" end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p th2j80ge3l6rbyuhcnk7nb4a3tkqn5f 803543 803542 2023-06-16T02:55:45Z en>Uzume 0 Undid revision 1160374075 by [[Special:Contributions/Uzume|Uzume]] ([[User talk:Uzume|talk]]) 803543 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. require("strict") local p = {} local arg = ... local i18n local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18n = require(title .. "/i18n").init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", authorNameString = "P2093", publisher = "P123", importedFrom = "P143", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local copyTable, parseDate -- forward declarations local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local prefix, title= "", nil if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id end prefix, title = "d:Special:EntityPage/", label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) -- XXX: should use fallback labels? end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id end end label = mw.text.nowiki(label) -- escape raw label text so it cannot be wikitext markup if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://geohack.toolforge.org/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local referenceEmpty = true -- will be set to false if at least one parameter is left unremoved local numAuthorParameters = 0 local numAuthorNameStringParameters = 0 local version = 2 -- increment this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[aliasesP.importedFrom] then statement.snaks[aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[aliasesP.inferredFrom] then statement.snaks[aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == aliasesP.author or i == aliasesP.authorNameString then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else referenceEmpty = false if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then -- handle non-author properties (and author properties ("author" and "author name string"), if they don't use the same template parameter) if (i ~= aliasesP.author and i ~= aliasesP.authorNameString) or (i18n['cite'][j][aliasesP.author] ~= i18n['cite'][j][aliasesP.authorNameString]) then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- to avoid problems with non-author multiple parameters (if existent), the following old code is retained for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end -- handle "author" and "author name string" specially if they use the same template parameter elseif i == aliasesP.author or i == aliasesP.authorNameString then if params[aliasesP.author] ~= nil then numAuthorParameters = #params[aliasesP.author] else numAuthorParameters = 0 end if params[aliasesP.authorNameString] ~= nil then numAuthorNameStringParameters = #params[aliasesP.authorNameString] else numAuthorNameStringParameters = 0 end -- execute only if both "author" and "author name string" satisfy this condition: the property is both in params and in statement.snaks or it is neither in params nor in statement.snaks -- reason: parameters are added to params each iteration of the loop, not before the loop if ((statement.snaks[aliasesP.author] == nil) == (numAuthorParameters == 0)) and ((statement.snaks[aliasesP.authorNameString] == nil) == (numAuthorNameStringParameters == 0)) then for k=1, numAuthorParameters + numAuthorNameStringParameters do if k <= numAuthorParameters then -- now handling the authors from the "author" property citeParams[j][i18n['cite'][j][aliasesP.author]..k] = label .. params[aliasesP.author][k] else -- now handling the authors from "author name string" citeParams[j][i18n['cite'][j][aliasesP.authorNameString]..k] = label .. params[aliasesP.authorNameString][k - numAuthorParameters] end end end end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) if the citation couldn't be displayed using Cite web or Cite Q, but has properties other than the removed ones, throw an error elseif not referenceEmpty then value = "<span style=\"color: crimson\">" .. errorText("malformed-reference") .. "</span>" end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p itbdurbcp43v78pdi053pmexne9l5dj 803544 803543 2023-06-16T03:04:32Z en>Uzume 0 reorder local module definitions 803544 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. require("strict") local p = {} local arg = ... local i18n local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18n = require(title .. "/i18n").init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", authorNameString = "P2093", publisher = "P123", importedFrom = "P143", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables local function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end local function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local prefix, title= "", nil if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id end prefix, title = "d:Special:EntityPage/", label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) -- XXX: should use fallback labels? end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id end end label = mw.text.nowiki(label) -- escape raw label text so it cannot be wikitext markup if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://geohack.toolforge.org/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local referenceEmpty = true -- will be set to false if at least one parameter is left unremoved local numAuthorParameters = 0 local numAuthorNameStringParameters = 0 local version = 2 -- increment this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[aliasesP.importedFrom] then statement.snaks[aliasesP.importedFrom] = nil end -- don't include "inferred from", which is added by a bot if statement.snaks[aliasesP.inferredFrom] then statement.snaks[aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == aliasesP.author or i == aliasesP.authorNameString then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else referenceEmpty = false if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then -- handle non-author properties (and author properties ("author" and "author name string"), if they don't use the same template parameter) if (i ~= aliasesP.author and i ~= aliasesP.authorNameString) or (i18n['cite'][j][aliasesP.author] ~= i18n['cite'][j][aliasesP.authorNameString]) then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- to avoid problems with non-author multiple parameters (if existent), the following old code is retained for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end -- handle "author" and "author name string" specially if they use the same template parameter elseif i == aliasesP.author or i == aliasesP.authorNameString then if params[aliasesP.author] ~= nil then numAuthorParameters = #params[aliasesP.author] else numAuthorParameters = 0 end if params[aliasesP.authorNameString] ~= nil then numAuthorNameStringParameters = #params[aliasesP.authorNameString] else numAuthorNameStringParameters = 0 end -- execute only if both "author" and "author name string" satisfy this condition: the property is both in params and in statement.snaks or it is neither in params nor in statement.snaks -- reason: parameters are added to params each iteration of the loop, not before the loop if ((statement.snaks[aliasesP.author] == nil) == (numAuthorParameters == 0)) and ((statement.snaks[aliasesP.authorNameString] == nil) == (numAuthorNameStringParameters == 0)) then for k=1, numAuthorParameters + numAuthorNameStringParameters do if k <= numAuthorParameters then -- now handling the authors from the "author" property citeParams[j][i18n['cite'][j][aliasesP.author]..k] = label .. params[aliasesP.author][k] else -- now handling the authors from "author name string" citeParams[j][i18n['cite'][j][aliasesP.authorNameString]..k] = label .. params[aliasesP.authorNameString][k - numAuthorParameters] end end end end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) if the citation couldn't be displayed using Cite web or Cite Q, but has properties other than the removed ones, throw an error elseif not referenceEmpty then value = "<span style=\"color: crimson\">" .. errorText("malformed-reference") .. "</span>" end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p dmx757zjxwjs3gyqisdta3qnyngdqd1 803545 756521 2023-06-24T01:54:55Z en>Pppery 0 Per edit request 803545 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. require("strict") local p = {} local arg = ... local i18n local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18n = require(title .. "/i18n").init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", authorNameString = "P2093", publisher = "P123", importedFrom = "P143", wikimediaImportURL = "P4656", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables local function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end local function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10) -- remove '+' sign (explicitly give base 10 to prevent error) if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local prefix, title= "", nil if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id end prefix, title = "d:Special:EntityPage/", label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) -- XXX: should use fallback labels? end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id end end label = mw.text.nowiki(label) -- escape raw label text so it cannot be wikitext markup if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^\+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://geohack.toolforge.org/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local referenceEmpty = true -- will be set to false if at least one parameter is left unremoved local numAuthorParameters = 0 local numAuthorNameStringParameters = 0 local version = 3 -- increment this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[aliasesP.importedFrom] then statement.snaks[aliasesP.importedFrom] = nil end -- don't include "Wikimedia import URL" if statement.snaks[aliasesP.wikimediaImportURL] then statement.snaks[aliasesP.wikimediaImportURL] = nil -- don't include "retrieved" if no "referenceURL" is present, -- as "retrieved" probably belongs to "Wikimedia import URL" if statement.snaks[aliasesP.retrieved] and not statement.snaks[aliasesP.referenceURL] then statement.snaks[aliasesP.retrieved] = nil end end -- don't include "inferred from", which is added by a bot if statement.snaks[aliasesP.inferredFrom] then statement.snaks[aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == aliasesP.author or i == aliasesP.authorNameString then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else referenceEmpty = false if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then -- handle non-author properties (and author properties ("author" and "author name string"), if they don't use the same template parameter) if (i ~= aliasesP.author and i ~= aliasesP.authorNameString) or (i18n['cite'][j][aliasesP.author] ~= i18n['cite'][j][aliasesP.authorNameString]) then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- to avoid problems with non-author multiple parameters (if existent), the following old code is retained for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end -- handle "author" and "author name string" specially if they use the same template parameter elseif i == aliasesP.author or i == aliasesP.authorNameString then if params[aliasesP.author] ~= nil then numAuthorParameters = #params[aliasesP.author] else numAuthorParameters = 0 end if params[aliasesP.authorNameString] ~= nil then numAuthorNameStringParameters = #params[aliasesP.authorNameString] else numAuthorNameStringParameters = 0 end -- execute only if both "author" and "author name string" satisfy this condition: the property is both in params and in statement.snaks or it is neither in params nor in statement.snaks -- reason: parameters are added to params each iteration of the loop, not before the loop if ((statement.snaks[aliasesP.author] == nil) == (numAuthorParameters == 0)) and ((statement.snaks[aliasesP.authorNameString] == nil) == (numAuthorNameStringParameters == 0)) then for k=1, numAuthorParameters + numAuthorNameStringParameters do if k <= numAuthorParameters then -- now handling the authors from the "author" property citeParams[j][i18n['cite'][j][aliasesP.author]..k] = label .. params[aliasesP.author][k] else -- now handling the authors from "author name string" citeParams[j][i18n['cite'][j][aliasesP.authorNameString]..k] = label .. params[aliasesP.authorNameString][k - numAuthorParameters] end end end end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) if the citation couldn't be displayed using Cite web or Cite Q, but has properties other than the removed ones, throw an error elseif not referenceEmpty then value = "<span style=\"color: crimson\">" .. errorText("malformed-reference") .. "</span>" end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p cs2sn2920oaaks2otf9hikheh86f6hy 803546 803545 2023-06-30T14:12:23Z en>MSGJ 0 bug fixes by [[User:Janhrach]] 803546 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. require("strict") local p = {} local arg = ... local i18n local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18n = require(title .. "/i18n").init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", authorNameString = "P2093", publisher = "P123", importedFrom = "P143", wikimediaImportURL = "P4656", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables local function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end local function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) -- explicitly give base 10 to prevent error if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local prefix, title= "", nil if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id end prefix, title = "d:Special:EntityPage/", label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) -- XXX: should use fallback labels? end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id end end label = mw.text.nowiki(label) -- escape raw label text so it cannot be wikitext markup if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^%+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://geohack.toolforge.org/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local referenceEmpty = true -- will be set to false if at least one parameter is left unremoved local numAuthorParameters = 0 local numAuthorNameStringParameters = 0 local version = 3 -- increment this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[aliasesP.importedFrom] then statement.snaks[aliasesP.importedFrom] = nil end -- don't include "Wikimedia import URL" if statement.snaks[aliasesP.wikimediaImportURL] then statement.snaks[aliasesP.wikimediaImportURL] = nil -- don't include "retrieved" if no "referenceURL" is present, -- as "retrieved" probably belongs to "Wikimedia import URL" if statement.snaks[aliasesP.retrieved] and not statement.snaks[aliasesP.referenceURL] then statement.snaks[aliasesP.retrieved] = nil end end -- don't include "inferred from", which is added by a bot if statement.snaks[aliasesP.inferredFrom] then statement.snaks[aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == aliasesP.author or i == aliasesP.authorNameString then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else referenceEmpty = false if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then -- handle non-author properties (and author properties ("author" and "author name string"), if they don't use the same template parameter) if (i ~= aliasesP.author and i ~= aliasesP.authorNameString) or (i18n['cite'][j][aliasesP.author] ~= i18n['cite'][j][aliasesP.authorNameString]) then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- to avoid problems with non-author multiple parameters (if existent), the following old code is retained for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end -- handle "author" and "author name string" specially if they use the same template parameter elseif i == aliasesP.author or i == aliasesP.authorNameString then if params[aliasesP.author] ~= nil then numAuthorParameters = #params[aliasesP.author] else numAuthorParameters = 0 end if params[aliasesP.authorNameString] ~= nil then numAuthorNameStringParameters = #params[aliasesP.authorNameString] else numAuthorNameStringParameters = 0 end -- execute only if both "author" and "author name string" satisfy this condition: the property is both in params and in statement.snaks or it is neither in params nor in statement.snaks -- reason: parameters are added to params each iteration of the loop, not before the loop if ((statement.snaks[aliasesP.author] == nil) == (numAuthorParameters == 0)) and ((statement.snaks[aliasesP.authorNameString] == nil) == (numAuthorNameStringParameters == 0)) then for k=1, numAuthorParameters + numAuthorNameStringParameters do if k <= numAuthorParameters then -- now handling the authors from the "author" property citeParams[j][i18n['cite'][j][aliasesP.author]..k] = label .. params[aliasesP.author][k] else -- now handling the authors from "author name string" citeParams[j][i18n['cite'][j][aliasesP.authorNameString]..k] = label .. params[aliasesP.authorNameString][k - numAuthorParameters] end end end end end else citeMismatch[j] = true end end end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) if the citation couldn't be displayed using Cite web or Cite Q, but has properties other than the removed ones, throw an error elseif not referenceEmpty then value = "<span style=\"color: crimson\">" .. errorText("malformed-reference") .. "</span>" end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p 6zuml6jvqcxm5u1sjjy487da1fohq8u 803547 803546 2023-07-05T16:41:33Z en>Paine Ellsworth 0 per edit request on talk page - use "subject named as" as title only when a URL is present 803547 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. require("strict") local p = {} local arg = ... local i18n local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18n = require(title .. "/i18n").init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", authorNameString = "P2093", publisher = "P123", importedFrom = "P143", wikimediaImportURL = "P4656", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903", subjectNamedAs = "P1810", wikidataProperty = "P1687" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables local function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end local function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) -- explicitly give base 10 to prevent error if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local prefix, title= "", nil if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id end prefix, title = "d:Special:EntityPage/", label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) -- XXX: should use fallback labels? end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id end end label = mw.text.nowiki(label) -- escape raw label text so it cannot be wikitext markup if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^%+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://geohack.toolforge.org/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local referenceEmpty = true -- will be set to false if at least one parameter is left unremoved local numAuthorParameters = 0 local numAuthorNameStringParameters = 0 local tempLink local additionalRefProperties = {} -- will hold properties of the reference which are not in statement.snaks, namely backup title from "subject named as" and URL from an external ID local wikidataPropertiesOfSource -- will contain "Wikidata property" properties of the item in stated in, if any local version = 4 -- increment this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[aliasesP.importedFrom] then statement.snaks[aliasesP.importedFrom] = nil end -- don't include "Wikimedia import URL" if statement.snaks[aliasesP.wikimediaImportURL] then statement.snaks[aliasesP.wikimediaImportURL] = nil -- don't include "retrieved" if no "referenceURL" is present, -- as "retrieved" probably belongs to "Wikimedia import URL" if statement.snaks[aliasesP.retrieved] and not statement.snaks[aliasesP.referenceURL] then statement.snaks[aliasesP.retrieved] = nil end end -- don't include "inferred from", which is added by a bot if statement.snaks[aliasesP.inferredFrom] then statement.snaks[aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end if statement.snaks[aliasesP.statedIn] and not statement.snaks[aliasesP.referenceURL] then -- "stated in" was given but "reference URL" was not. -- get "Wikidata property" properties from the item in "stated in" -- if any of the returned properties of the external-id datatype is in statement.snaks, generate a URL from it and use the URL in the reference -- find the "Wikidata property" properties in the item from "stated in" wikidataPropertiesOfSource = mw.text.split(p._properties{p.flags.raw, aliasesP.wikidataProperty, [p.args.eid] = self.conf:getValue(statement.snaks[aliasesP.statedIn][1], true, false)}, ", ", true) for i, wikidataPropertyOfSource in pairs(wikidataPropertiesOfSource) do if statement.snaks[wikidataPropertyOfSource] and statement.snaks[wikidataPropertyOfSource][1].datatype == "external-id" then tempLink = self.conf:getValue(statement.snaks[wikidataPropertyOfSource][1], false, true) -- not raw, linked if mw.ustring.match(tempLink, "^%[%Z- %Z+%]$") then -- getValue returned a URL. additionalRefProperties[aliasesP.referenceURL] = mw.ustring.gsub(tempLink, "^%[(%Z-) %Z+%]$", "%1") -- the URL is in wiki markup, so strip the square brackets and the display text statement.snaks[wikidataPropertyOfSource] = nil break end end end end -- don't include "subject named as", but use it as the title when "title" is not present but a URL is if statement.snaks[aliasesP.subjectNamedAs] then if not statement.snaks[aliasesP.title] and (statement.snaks[aliasesP.referenceURL] or additionalRefProperties[aliasesP.referenceURL]) then additionalRefProperties[aliasesP.title] = statement.snaks[aliasesP.subjectNamedAs][1].datavalue.value end statement.snaks[aliasesP.subjectNamedAs] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == aliasesP.author or i == aliasesP.authorNameString then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else referenceEmpty = false if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then -- handle non-author properties (and author properties ("author" and "author name string"), if they don't use the same template parameter) if (i ~= aliasesP.author and i ~= aliasesP.authorNameString) or (i18n['cite'][j][aliasesP.author] ~= i18n['cite'][j][aliasesP.authorNameString]) then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- to avoid problems with non-author multiple parameters (if existent), the following old code is retained for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end -- handle "author" and "author name string" specially if they use the same template parameter elseif i == aliasesP.author or i == aliasesP.authorNameString then if params[aliasesP.author] ~= nil then numAuthorParameters = #params[aliasesP.author] else numAuthorParameters = 0 end if params[aliasesP.authorNameString] ~= nil then numAuthorNameStringParameters = #params[aliasesP.authorNameString] else numAuthorNameStringParameters = 0 end -- execute only if both "author" and "author name string" satisfy this condition: the property is both in params and in statement.snaks or it is neither in params nor in statement.snaks -- reason: parameters are added to params each iteration of the loop, not before the loop if ((statement.snaks[aliasesP.author] == nil) == (numAuthorParameters == 0)) and ((statement.snaks[aliasesP.authorNameString] == nil) == (numAuthorNameStringParameters == 0)) then for k=1, numAuthorParameters + numAuthorNameStringParameters do if k <= numAuthorParameters then -- now handling the authors from the "author" property citeParams[j][i18n['cite'][j][aliasesP.author]..k] = label .. params[aliasesP.author][k] else -- now handling the authors from "author name string" citeParams[j][i18n['cite'][j][aliasesP.authorNameString]..k] = label .. params[aliasesP.authorNameString][k - numAuthorParameters] end end end end end else citeMismatch[j] = true end end end end end -- use additional properties for i in pairs(additionalRefProperties) do for j in pairs(citeParams) do if not citeMismatch[j] and i18n["cite"][j][i] then citeParams[j][i18n["cite"][j][i]] = additionalRefProperties[i] else citeMismatch[j] = true end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) if the citation couldn't be displayed using Cite web or Cite Q, but has properties other than the removed ones, throw an error elseif not referenceEmpty then value = "<span style=\"color: crimson\">" .. errorText("malformed-reference") .. "</span>" end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p nes30vv63ljt56gmmqn6e3gut2tls1k 803548 803547 2023-07-10T18:06:15Z en>Pppery 0 Per edit request 803548 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. require("strict") local p = {} local arg = ... local i18n local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18n = require(title .. "/i18n").init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", authorNameString = "P2093", publisher = "P123", importedFrom = "P143", wikimediaImportURL = "P4656", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903", subjectNamedAs = "P1810", wikidataProperty = "P1687", publishedIn = "P1433" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables local function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end local function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) -- explicitly give base 10 to prevent error if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local prefix, title= "", nil if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id end prefix, title = "d:Special:EntityPage/", label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) -- XXX: should use fallback labels? end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id end end label = mw.text.nowiki(label) -- escape raw label text so it cannot be wikitext markup if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^%+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://geohack.toolforge.org/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local referenceEmpty = true -- will be set to false if at least one parameter is left unremoved local numAuthorParameters = 0 local numAuthorNameStringParameters = 0 local tempLink local additionalRefProperties = {} -- will hold properties of the reference which are not in statement.snaks, namely backup title from "subject named as" and URL from an external ID local wikidataPropertiesOfSource -- will contain "Wikidata property" properties of the item in stated in, if any local version = 4 -- increment this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[aliasesP.importedFrom] then statement.snaks[aliasesP.importedFrom] = nil end -- don't include "Wikimedia import URL" if statement.snaks[aliasesP.wikimediaImportURL] then statement.snaks[aliasesP.wikimediaImportURL] = nil -- don't include "retrieved" if no "referenceURL" is present, -- as "retrieved" probably belongs to "Wikimedia import URL" if statement.snaks[aliasesP.retrieved] and not statement.snaks[aliasesP.referenceURL] then statement.snaks[aliasesP.retrieved] = nil end end -- don't include "inferred from", which is added by a bot if statement.snaks[aliasesP.inferredFrom] then statement.snaks[aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end if statement.snaks[aliasesP.statedIn] and not statement.snaks[aliasesP.referenceURL] then -- "stated in" was given but "reference URL" was not. -- get "Wikidata property" properties from the item in "stated in" -- if any of the returned properties of the external-id datatype is in statement.snaks, generate a URL from it and use the URL in the reference -- find the "Wikidata property" properties in the item from "stated in" wikidataPropertiesOfSource = mw.text.split(p._properties{p.flags.raw, aliasesP.wikidataProperty, [p.args.eid] = self.conf:getValue(statement.snaks[aliasesP.statedIn][1], true, false)}, ", ", true) for i, wikidataPropertyOfSource in pairs(wikidataPropertiesOfSource) do if statement.snaks[wikidataPropertyOfSource] and statement.snaks[wikidataPropertyOfSource][1].datatype == "external-id" then tempLink = self.conf:getValue(statement.snaks[wikidataPropertyOfSource][1], false, true) -- not raw, linked if mw.ustring.match(tempLink, "^%[%Z- %Z+%]$") then -- getValue returned a URL. additionalRefProperties[aliasesP.referenceURL] = mw.ustring.gsub(tempLink, "^%[(%Z-) %Z+%]$", "%1") -- the URL is in wiki markup, so strip the square brackets and the display text statement.snaks[wikidataPropertyOfSource] = nil break end end end end -- don't include "subject named as", but use it as the title when "title" is not present but a URL is if statement.snaks[aliasesP.subjectNamedAs] then if not statement.snaks[aliasesP.title] and (statement.snaks[aliasesP.referenceURL] or additionalRefProperties[aliasesP.referenceURL]) then additionalRefProperties[aliasesP.title] = statement.snaks[aliasesP.subjectNamedAs][1].datavalue.value end statement.snaks[aliasesP.subjectNamedAs] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == aliasesP.author or i == aliasesP.authorNameString then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else referenceEmpty = false if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then -- handle non-author properties (and author properties ("author" and "author name string"), if they don't use the same template parameter) if (i ~= aliasesP.author and i ~= aliasesP.authorNameString) or (i18n['cite'][j][aliasesP.author] ~= i18n['cite'][j][aliasesP.authorNameString]) then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- to avoid problems with non-author multiple parameters (if existent), the following old code is retained for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end -- handle "author" and "author name string" specially if they use the same template parameter elseif i == aliasesP.author or i == aliasesP.authorNameString then if params[aliasesP.author] ~= nil then numAuthorParameters = #params[aliasesP.author] else numAuthorParameters = 0 end if params[aliasesP.authorNameString] ~= nil then numAuthorNameStringParameters = #params[aliasesP.authorNameString] else numAuthorNameStringParameters = 0 end -- execute only if both "author" and "author name string" satisfy this condition: the property is both in params and in statement.snaks or it is neither in params nor in statement.snaks -- reason: parameters are added to params each iteration of the loop, not before the loop if ((statement.snaks[aliasesP.author] == nil) == (numAuthorParameters == 0)) and ((statement.snaks[aliasesP.authorNameString] == nil) == (numAuthorNameStringParameters == 0)) then for k=1, numAuthorParameters + numAuthorNameStringParameters do if k <= numAuthorParameters then -- now handling the authors from the "author" property citeParams[j][i18n['cite'][j][aliasesP.author]..k] = label .. params[aliasesP.author][k] else -- now handling the authors from "author name string" citeParams[j][i18n['cite'][j][aliasesP.authorNameString]..k] = label .. params[aliasesP.authorNameString][k - numAuthorParameters] end end end end end else citeMismatch[j] = true end end end end end -- use additional properties for i in pairs(additionalRefProperties) do for j in pairs(citeParams) do if not citeMismatch[j] and i18n["cite"][j][i] then citeParams[j][i18n["cite"][j][i]] = additionalRefProperties[i] else citeMismatch[j] = true end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) if the citation couldn't be displayed using Cite web or Cite Q, but has properties other than the removed ones, throw an error elseif not referenceEmpty then value = "<span style=\"color: crimson\">" .. errorText("malformed-reference") .. "</span>" end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p jnrxu2rnvu16g4zg8thr6m4pwwr3r9p 803549 803548 2023-10-26T14:35:32Z en>Jonesey95 0 Change error message color per talk page 803549 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. require("strict") local p = {} local arg = ... local i18n local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18n = require(title .. "/i18n").init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", authorNameString = "P2093", publisher = "P123", importedFrom = "P143", wikimediaImportURL = "P4656", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903", subjectNamedAs = "P1810", wikidataProperty = "P1687", publishedIn = "P1433" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables local function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end local function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) -- explicitly give base 10 to prevent error if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local prefix, title= "", nil if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id end prefix, title = "d:Special:EntityPage/", label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) -- XXX: should use fallback labels? end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id end end label = mw.text.nowiki(label) -- escape raw label text so it cannot be wikitext markup if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^%+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://geohack.toolforge.org/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local referenceEmpty = true -- will be set to false if at least one parameter is left unremoved local numAuthorParameters = 0 local numAuthorNameStringParameters = 0 local tempLink local additionalRefProperties = {} -- will hold properties of the reference which are not in statement.snaks, namely backup title from "subject named as" and URL from an external ID local wikidataPropertiesOfSource -- will contain "Wikidata property" properties of the item in stated in, if any local version = 4 -- increment this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[aliasesP.importedFrom] then statement.snaks[aliasesP.importedFrom] = nil end -- don't include "Wikimedia import URL" if statement.snaks[aliasesP.wikimediaImportURL] then statement.snaks[aliasesP.wikimediaImportURL] = nil -- don't include "retrieved" if no "referenceURL" is present, -- as "retrieved" probably belongs to "Wikimedia import URL" if statement.snaks[aliasesP.retrieved] and not statement.snaks[aliasesP.referenceURL] then statement.snaks[aliasesP.retrieved] = nil end end -- don't include "inferred from", which is added by a bot if statement.snaks[aliasesP.inferredFrom] then statement.snaks[aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end if statement.snaks[aliasesP.statedIn] and not statement.snaks[aliasesP.referenceURL] then -- "stated in" was given but "reference URL" was not. -- get "Wikidata property" properties from the item in "stated in" -- if any of the returned properties of the external-id datatype is in statement.snaks, generate a URL from it and use the URL in the reference -- find the "Wikidata property" properties in the item from "stated in" wikidataPropertiesOfSource = mw.text.split(p._properties{p.flags.raw, aliasesP.wikidataProperty, [p.args.eid] = self.conf:getValue(statement.snaks[aliasesP.statedIn][1], true, false)}, ", ", true) for i, wikidataPropertyOfSource in pairs(wikidataPropertiesOfSource) do if statement.snaks[wikidataPropertyOfSource] and statement.snaks[wikidataPropertyOfSource][1].datatype == "external-id" then tempLink = self.conf:getValue(statement.snaks[wikidataPropertyOfSource][1], false, true) -- not raw, linked if mw.ustring.match(tempLink, "^%[%Z- %Z+%]$") then -- getValue returned a URL. additionalRefProperties[aliasesP.referenceURL] = mw.ustring.gsub(tempLink, "^%[(%Z-) %Z+%]$", "%1") -- the URL is in wiki markup, so strip the square brackets and the display text statement.snaks[wikidataPropertyOfSource] = nil break end end end end -- don't include "subject named as", but use it as the title when "title" is not present but a URL is if statement.snaks[aliasesP.subjectNamedAs] then if not statement.snaks[aliasesP.title] and (statement.snaks[aliasesP.referenceURL] or additionalRefProperties[aliasesP.referenceURL]) then additionalRefProperties[aliasesP.title] = statement.snaks[aliasesP.subjectNamedAs][1].datavalue.value end statement.snaks[aliasesP.subjectNamedAs] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == aliasesP.author or i == aliasesP.authorNameString then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else referenceEmpty = false if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then -- handle non-author properties (and author properties ("author" and "author name string"), if they don't use the same template parameter) if (i ~= aliasesP.author and i ~= aliasesP.authorNameString) or (i18n['cite'][j][aliasesP.author] ~= i18n['cite'][j][aliasesP.authorNameString]) then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- to avoid problems with non-author multiple parameters (if existent), the following old code is retained for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end -- handle "author" and "author name string" specially if they use the same template parameter elseif i == aliasesP.author or i == aliasesP.authorNameString then if params[aliasesP.author] ~= nil then numAuthorParameters = #params[aliasesP.author] else numAuthorParameters = 0 end if params[aliasesP.authorNameString] ~= nil then numAuthorNameStringParameters = #params[aliasesP.authorNameString] else numAuthorNameStringParameters = 0 end -- execute only if both "author" and "author name string" satisfy this condition: the property is both in params and in statement.snaks or it is neither in params nor in statement.snaks -- reason: parameters are added to params each iteration of the loop, not before the loop if ((statement.snaks[aliasesP.author] == nil) == (numAuthorParameters == 0)) and ((statement.snaks[aliasesP.authorNameString] == nil) == (numAuthorNameStringParameters == 0)) then for k=1, numAuthorParameters + numAuthorNameStringParameters do if k <= numAuthorParameters then -- now handling the authors from the "author" property citeParams[j][i18n['cite'][j][aliasesP.author]..k] = label .. params[aliasesP.author][k] else -- now handling the authors from "author name string" citeParams[j][i18n['cite'][j][aliasesP.authorNameString]..k] = label .. params[aliasesP.authorNameString][k - numAuthorParameters] end end end end end else citeMismatch[j] = true end end end end end -- use additional properties for i in pairs(additionalRefProperties) do for j in pairs(citeParams) do if not citeMismatch[j] and i18n["cite"][j][i] then citeParams[j][i18n["cite"][j][i]] = additionalRefProperties[i] else citeMismatch[j] = true end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) if the citation couldn't be displayed using Cite web or Cite Q, but has properties other than the removed ones, throw an error elseif not referenceEmpty then value = "<span style=\"color:#dd3333\">" .. errorText("malformed-reference") .. "</span>" end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p rbyyhay8jcfmk7t1lm1on7y4uwpt1fs 803550 803549 2023-10-26T14:36:14Z en>Jonesey95 0 update version number per talk page 803550 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. require("strict") local p = {} local arg = ... local i18n local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18n = require(title .. "/i18n").init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", authorNameString = "P2093", publisher = "P123", importedFrom = "P143", wikimediaImportURL = "P4656", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903", subjectNamedAs = "P1810", wikidataProperty = "P1687", publishedIn = "P1433" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables local function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end local function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) -- explicitly give base 10 to prevent error if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local prefix, title= "", nil if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id end prefix, title = "d:Special:EntityPage/", label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) -- XXX: should use fallback labels? end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id end end label = mw.text.nowiki(label) -- escape raw label text so it cannot be wikitext markup if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^%+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://geohack.toolforge.org/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local referenceEmpty = true -- will be set to false if at least one parameter is left unremoved local numAuthorParameters = 0 local numAuthorNameStringParameters = 0 local tempLink local additionalRefProperties = {} -- will hold properties of the reference which are not in statement.snaks, namely backup title from "subject named as" and URL from an external ID local wikidataPropertiesOfSource -- will contain "Wikidata property" properties of the item in stated in, if any local version = 5 -- increment this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[aliasesP.importedFrom] then statement.snaks[aliasesP.importedFrom] = nil end -- don't include "Wikimedia import URL" if statement.snaks[aliasesP.wikimediaImportURL] then statement.snaks[aliasesP.wikimediaImportURL] = nil -- don't include "retrieved" if no "referenceURL" is present, -- as "retrieved" probably belongs to "Wikimedia import URL" if statement.snaks[aliasesP.retrieved] and not statement.snaks[aliasesP.referenceURL] then statement.snaks[aliasesP.retrieved] = nil end end -- don't include "inferred from", which is added by a bot if statement.snaks[aliasesP.inferredFrom] then statement.snaks[aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end if statement.snaks[aliasesP.statedIn] and not statement.snaks[aliasesP.referenceURL] then -- "stated in" was given but "reference URL" was not. -- get "Wikidata property" properties from the item in "stated in" -- if any of the returned properties of the external-id datatype is in statement.snaks, generate a URL from it and use the URL in the reference -- find the "Wikidata property" properties in the item from "stated in" wikidataPropertiesOfSource = mw.text.split(p._properties{p.flags.raw, aliasesP.wikidataProperty, [p.args.eid] = self.conf:getValue(statement.snaks[aliasesP.statedIn][1], true, false)}, ", ", true) for i, wikidataPropertyOfSource in pairs(wikidataPropertiesOfSource) do if statement.snaks[wikidataPropertyOfSource] and statement.snaks[wikidataPropertyOfSource][1].datatype == "external-id" then tempLink = self.conf:getValue(statement.snaks[wikidataPropertyOfSource][1], false, true) -- not raw, linked if mw.ustring.match(tempLink, "^%[%Z- %Z+%]$") then -- getValue returned a URL. additionalRefProperties[aliasesP.referenceURL] = mw.ustring.gsub(tempLink, "^%[(%Z-) %Z+%]$", "%1") -- the URL is in wiki markup, so strip the square brackets and the display text statement.snaks[wikidataPropertyOfSource] = nil break end end end end -- don't include "subject named as", but use it as the title when "title" is not present but a URL is if statement.snaks[aliasesP.subjectNamedAs] then if not statement.snaks[aliasesP.title] and (statement.snaks[aliasesP.referenceURL] or additionalRefProperties[aliasesP.referenceURL]) then additionalRefProperties[aliasesP.title] = statement.snaks[aliasesP.subjectNamedAs][1].datavalue.value end statement.snaks[aliasesP.subjectNamedAs] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == aliasesP.author or i == aliasesP.authorNameString then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else referenceEmpty = false if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then -- handle non-author properties (and author properties ("author" and "author name string"), if they don't use the same template parameter) if (i ~= aliasesP.author and i ~= aliasesP.authorNameString) or (i18n['cite'][j][aliasesP.author] ~= i18n['cite'][j][aliasesP.authorNameString]) then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- to avoid problems with non-author multiple parameters (if existent), the following old code is retained for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end -- handle "author" and "author name string" specially if they use the same template parameter elseif i == aliasesP.author or i == aliasesP.authorNameString then if params[aliasesP.author] ~= nil then numAuthorParameters = #params[aliasesP.author] else numAuthorParameters = 0 end if params[aliasesP.authorNameString] ~= nil then numAuthorNameStringParameters = #params[aliasesP.authorNameString] else numAuthorNameStringParameters = 0 end -- execute only if both "author" and "author name string" satisfy this condition: the property is both in params and in statement.snaks or it is neither in params nor in statement.snaks -- reason: parameters are added to params each iteration of the loop, not before the loop if ((statement.snaks[aliasesP.author] == nil) == (numAuthorParameters == 0)) and ((statement.snaks[aliasesP.authorNameString] == nil) == (numAuthorNameStringParameters == 0)) then for k=1, numAuthorParameters + numAuthorNameStringParameters do if k <= numAuthorParameters then -- now handling the authors from the "author" property citeParams[j][i18n['cite'][j][aliasesP.author]..k] = label .. params[aliasesP.author][k] else -- now handling the authors from "author name string" citeParams[j][i18n['cite'][j][aliasesP.authorNameString]..k] = label .. params[aliasesP.authorNameString][k - numAuthorParameters] end end end end end else citeMismatch[j] = true end end end end end -- use additional properties for i in pairs(additionalRefProperties) do for j in pairs(citeParams) do if not citeMismatch[j] and i18n["cite"][j][i] then citeParams[j][i18n["cite"][j][i]] = additionalRefProperties[i] else citeMismatch[j] = true end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) if the citation couldn't be displayed using Cite web or Cite Q, but has properties other than the removed ones, throw an error elseif not referenceEmpty then value = "<span style=\"color:#dd3333\">" .. errorText("malformed-reference") .. "</span>" end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p 4miawu63vlr3fz3fyhdaljxph8rodbo 803551 803550 2023-12-22T23:23:29Z en>Pppery 0 Per edit request 803551 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. require("strict") local p = {} local arg = ... local i18n local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18n = require(title .. "/i18n").init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date", globalSiteId = "globalSiteId" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", authorNameString = "P2093", publisher = "P123", importedFrom = "P143", wikimediaImportURL = "P4656", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903", subjectNamedAs = "P1810", wikidataProperty = "P1687", publishedIn = "P1433" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables local function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end local function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) -- explicitly give base 10 to prevent error if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local prefix, title= "", nil if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id end prefix, title = "d:Special:EntityPage/", label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) -- XXX: should use fallback labels? end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id end end label = mw.text.nowiki(label) -- escape raw label text so it cannot be wikitext markup if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^%+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://geohack.toolforge.org/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local referenceEmpty = true -- will be set to false if at least one parameter is left unremoved local numAuthorParameters = 0 local numAuthorNameStringParameters = 0 local tempLink local additionalRefProperties = {} -- will hold properties of the reference which are not in statement.snaks, namely backup title from "subject named as" and URL from an external ID local wikidataPropertiesOfSource -- will contain "Wikidata property" properties of the item in stated in, if any local version = 5 -- increment this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[aliasesP.importedFrom] then statement.snaks[aliasesP.importedFrom] = nil end -- don't include "Wikimedia import URL" if statement.snaks[aliasesP.wikimediaImportURL] then statement.snaks[aliasesP.wikimediaImportURL] = nil -- don't include "retrieved" if no "referenceURL" is present, -- as "retrieved" probably belongs to "Wikimedia import URL" if statement.snaks[aliasesP.retrieved] and not statement.snaks[aliasesP.referenceURL] then statement.snaks[aliasesP.retrieved] = nil end end -- don't include "inferred from", which is added by a bot if statement.snaks[aliasesP.inferredFrom] then statement.snaks[aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end if statement.snaks[aliasesP.statedIn] and not statement.snaks[aliasesP.referenceURL] then -- "stated in" was given but "reference URL" was not. -- get "Wikidata property" properties from the item in "stated in" -- if any of the returned properties of the external-id datatype is in statement.snaks, generate a URL from it and use the URL in the reference -- find the "Wikidata property" properties in the item from "stated in" wikidataPropertiesOfSource = mw.text.split(p._properties{p.flags.raw, aliasesP.wikidataProperty, [p.args.eid] = self.conf:getValue(statement.snaks[aliasesP.statedIn][1], true, false)}, ", ", true) for i, wikidataPropertyOfSource in pairs(wikidataPropertiesOfSource) do if statement.snaks[wikidataPropertyOfSource] and statement.snaks[wikidataPropertyOfSource][1].datatype == "external-id" then tempLink = self.conf:getValue(statement.snaks[wikidataPropertyOfSource][1], false, true) -- not raw, linked if mw.ustring.match(tempLink, "^%[%Z- %Z+%]$") then -- getValue returned a URL. additionalRefProperties[aliasesP.referenceURL] = mw.ustring.gsub(tempLink, "^%[(%Z-) %Z+%]$", "%1") -- the URL is in wiki markup, so strip the square brackets and the display text statement.snaks[wikidataPropertyOfSource] = nil break end end end end -- don't include "subject named as", but use it as the title when "title" is not present but a URL is if statement.snaks[aliasesP.subjectNamedAs] then if not statement.snaks[aliasesP.title] and (statement.snaks[aliasesP.referenceURL] or additionalRefProperties[aliasesP.referenceURL]) then additionalRefProperties[aliasesP.title] = statement.snaks[aliasesP.subjectNamedAs][1].datavalue.value end statement.snaks[aliasesP.subjectNamedAs] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == aliasesP.author or i == aliasesP.authorNameString then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, (self.linked or (i == aliasesP.statedIn)) and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else referenceEmpty = false if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then -- handle non-author properties (and author properties ("author" and "author name string"), if they don't use the same template parameter) if (i ~= aliasesP.author and i ~= aliasesP.authorNameString) or (i18n['cite'][j][aliasesP.author] ~= i18n['cite'][j][aliasesP.authorNameString]) then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- to avoid problems with non-author multiple parameters (if existent), the following old code is retained for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end -- handle "author" and "author name string" specially if they use the same template parameter elseif i == aliasesP.author or i == aliasesP.authorNameString then if params[aliasesP.author] ~= nil then numAuthorParameters = #params[aliasesP.author] else numAuthorParameters = 0 end if params[aliasesP.authorNameString] ~= nil then numAuthorNameStringParameters = #params[aliasesP.authorNameString] else numAuthorNameStringParameters = 0 end -- execute only if both "author" and "author name string" satisfy this condition: the property is both in params and in statement.snaks or it is neither in params nor in statement.snaks -- reason: parameters are added to params each iteration of the loop, not before the loop if ((statement.snaks[aliasesP.author] == nil) == (numAuthorParameters == 0)) and ((statement.snaks[aliasesP.authorNameString] == nil) == (numAuthorNameStringParameters == 0)) then for k=1, numAuthorParameters + numAuthorNameStringParameters do if k <= numAuthorParameters then -- now handling the authors from the "author" property citeParams[j][i18n['cite'][j][aliasesP.author]..k] = label .. params[aliasesP.author][k] else -- now handling the authors from "author name string" citeParams[j][i18n['cite'][j][aliasesP.authorNameString]..k] = label .. params[aliasesP.authorNameString][k - numAuthorParameters] end end end end end else citeMismatch[j] = true end end end end end -- use additional properties for i in pairs(additionalRefProperties) do for j in pairs(citeParams) do if not citeMismatch[j] and i18n["cite"][j][i] then citeParams[j][i18n["cite"][j][i]] = additionalRefProperties[i] else citeMismatch[j] = true end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][aliasesP.title]] then useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then -- we need the raw "stated in" Q-identifier for the this template citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true) -- raw = true useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) if the citation couldn't be displayed using Cite web or Cite Q, but has properties other than the removed ones, throw an error elseif not referenceEmpty then value = "<span style=\"color:#dd3333\">" .. errorText("malformed-reference") .. "</span>" end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix, globalSiteId) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page, globalSiteId) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], false, args[p.args.globalSiteId]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true, args[p.args.globalSiteId]) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p 3cdxbtakyq00gfjfyegmimoqud2n0fc 803552 803551 2024-04-01T08:09:28Z en>Janhrach 0 Update from sandbox, see talk 803552 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. require("strict") local p = {} local arg = ... local i18n local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18n = require(title .. "/i18n").init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date", globalSiteId = "globalSiteId" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", authorNameString = "P2093", publisher = "P123", importedFrom = "P143", wikimediaImportURL = "P4656", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903", subjectNamedAs = "P1810", wikidataProperty = "P1687", publishedIn = "P1433" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables local function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end local function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) -- explicitly give base 10 to prevent error if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local prefix, title= "", nil if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id end prefix, title = "d:Special:EntityPage/", label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) -- XXX: should use fallback labels? end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id end end label = mw.text.nowiki(label) -- escape raw label text so it cannot be wikitext markup if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^%+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://geohack.toolforge.org/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local referenceEmpty = true -- will be set to false if at least one parameter is left unremoved local numAuthorParameters = 0 local numAuthorNameStringParameters = 0 local tempLink local additionalRefProperties = {} -- will hold properties of the reference which are not in statement.snaks, namely backup title from "subject named as" and link from an external ID local wikidataPropertiesOfSource -- will contain "Wikidata property" properties of the item in stated in, if any local version = 6 -- increment this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[aliasesP.importedFrom] then statement.snaks[aliasesP.importedFrom] = nil end -- don't include "Wikimedia import URL" if statement.snaks[aliasesP.wikimediaImportURL] then statement.snaks[aliasesP.wikimediaImportURL] = nil -- don't include "retrieved" if no "referenceURL" is present, -- as "retrieved" probably belongs to "Wikimedia import URL" if statement.snaks[aliasesP.retrieved] and not statement.snaks[aliasesP.referenceURL] then statement.snaks[aliasesP.retrieved] = nil end end -- don't include "inferred from", which is added by a bot if statement.snaks[aliasesP.inferredFrom] then statement.snaks[aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end if statement.snaks[aliasesP.statedIn] and not statement.snaks[aliasesP.referenceURL] then -- "stated in" was given but "reference URL" was not. -- get "Wikidata property" properties from the item in "stated in" -- if any of the returned properties of the external-id datatype is in statement.snaks, generate a link from it and use the link in the reference -- find the "Wikidata property" properties in the item from "stated in" wikidataPropertiesOfSource = mw.text.split(p._properties{p.flags.raw, aliasesP.wikidataProperty, [p.args.eid] = self.conf:getValue(statement.snaks[aliasesP.statedIn][1], true, false)}, ", ", true) for i, wikidataPropertyOfSource in pairs(wikidataPropertiesOfSource) do if statement.snaks[wikidataPropertyOfSource] and statement.snaks[wikidataPropertyOfSource][1].datatype == "external-id" then tempLink = self.conf:getValue(statement.snaks[wikidataPropertyOfSource][1], false, true) -- not raw, linked if mw.ustring.match(tempLink, "^%[%Z- %Z+%]$") then -- getValue returned a URL. additionalRefProperties[aliasesP.referenceURL] = mw.ustring.gsub(tempLink, "^%[(%Z-) %Z+%]$", "%1") -- the link is in wiki markup, so strip the square brackets and the display text statement.snaks[wikidataPropertyOfSource] = nil break end end end end -- don't include "subject named as", but use it as the title when "title" is not present but a URL is if statement.snaks[aliasesP.subjectNamedAs] then if not statement.snaks[aliasesP.title] and (statement.snaks[aliasesP.referenceURL] or additionalRefProperties[aliasesP.referenceURL]) then additionalRefProperties[aliasesP.title] = statement.snaks[aliasesP.subjectNamedAs][1].datavalue.value end statement.snaks[aliasesP.subjectNamedAs] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == aliasesP.author or i == aliasesP.authorNameString then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true elseif i == aliasesP.statedIn then -- Get "stated in" raw, as it is wanted (for Cite Q) even if it doesn't have a local language label. params[aliasesP.statedIn] = {self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true)} -- raw = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, self.linked and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else referenceEmpty = false if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then -- handle non-author properties (and author properties ("author" and "author name string"), if they don't use the same template parameter) if (i ~= aliasesP.author and i ~= aliasesP.authorNameString) or (i18n['cite'][j][aliasesP.author] ~= i18n['cite'][j][aliasesP.authorNameString]) then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- to avoid problems with non-author multiple parameters (if existent), the following old code is retained for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end -- handle "author" and "author name string" specially if they use the same template parameter elseif i == aliasesP.author or i == aliasesP.authorNameString then if params[aliasesP.author] ~= nil then numAuthorParameters = #params[aliasesP.author] else numAuthorParameters = 0 end if params[aliasesP.authorNameString] ~= nil then numAuthorNameStringParameters = #params[aliasesP.authorNameString] else numAuthorNameStringParameters = 0 end -- execute only if both "author" and "author name string" satisfy this condition: the property is both in params and in statement.snaks or it is neither in params nor in statement.snaks -- reason: parameters are added to params each iteration of the loop, not before the loop if ((statement.snaks[aliasesP.author] == nil) == (numAuthorParameters == 0)) and ((statement.snaks[aliasesP.authorNameString] == nil) == (numAuthorNameStringParameters == 0)) then for k=1, numAuthorParameters + numAuthorNameStringParameters do if k <= numAuthorParameters then -- now handling the authors from the "author" property citeParams[j][i18n['cite'][j][aliasesP.author]..k] = label .. params[aliasesP.author][k] else -- now handling the authors from "author name string" citeParams[j][i18n['cite'][j][aliasesP.authorNameString]..k] = label .. params[aliasesP.authorNameString][k - numAuthorParameters] end end end end end else citeMismatch[j] = true end end end end end -- use additional properties for i in pairs(additionalRefProperties) do for j in pairs(citeParams) do if not citeMismatch[j] and i18n["cite"][j][i] then citeParams[j][i18n["cite"][j][i]] = additionalRefProperties[i] else citeMismatch[j] = true end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least both "reference URL" and "title" are present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] and citeParams['web'][i18n['cite']['web'][aliasesP.title]] then -- we need a processed "stated in" for this template citeParams['web'][i18n['cite']['web'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, false, self.linked, true) useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) if the citation couldn't be displayed using Cite web or Cite Q, but has properties other than the removed ones, throw an error elseif not referenceEmpty then value = "<span style=\"color:#dd3333\">" .. errorText("malformed-reference") .. "</span>" end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix, globalSiteId) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page, globalSiteId) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], false, args[p.args.globalSiteId]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true, args[p.args.globalSiteId]) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p 6xowqousmlqn8snhh91c9p6t57np0h7 803553 803552 2024-06-10T19:17:03Z en>Janhrach 0 Update from sandbox 803553 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. require("strict") local p = {} local arg = ... local i18n local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18n = require(title .. "/i18n").init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date", globalSiteId = "globalSiteId" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", authorNameString = "P2093", publisher = "P123", importedFrom = "P143", wikimediaImportURL = "P4656", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903", subjectNamedAs = "P1810", wikidataProperty = "P1687", publishedIn = "P1433" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables local function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end local function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) -- explicitly give base 10 to prevent error if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local prefix, title= "", nil if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id end prefix, title = "d:Special:EntityPage/", label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) -- XXX: should use fallback labels? end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id end end label = mw.text.nowiki(label) -- escape raw label text so it cannot be wikitext markup if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^%+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://geohack.toolforge.org/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local referenceEmpty = true -- will be set to false if at least one parameter is left unremoved local numAuthorParameters = 0 local numAuthorNameStringParameters = 0 local tempLink local additionalRefProperties = {} -- will hold properties of the reference which are not in statement.snaks, namely backup title from "subject named as" and link from an external ID local wikidataPropertiesOfSource -- will contain "Wikidata property" properties of the item in stated in, if any local version = 7 -- increment this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't include "imported from", which is added by a bot if statement.snaks[aliasesP.importedFrom] then statement.snaks[aliasesP.importedFrom] = nil end -- don't include "Wikimedia import URL" if statement.snaks[aliasesP.wikimediaImportURL] then statement.snaks[aliasesP.wikimediaImportURL] = nil -- don't include "retrieved" if no "referenceURL" is present, -- as "retrieved" probably belongs to "Wikimedia import URL" if statement.snaks[aliasesP.retrieved] and not statement.snaks[aliasesP.referenceURL] then statement.snaks[aliasesP.retrieved] = nil end end -- don't include "inferred from", which is added by a bot if statement.snaks[aliasesP.inferredFrom] then statement.snaks[aliasesP.inferredFrom] = nil end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end if statement.snaks[aliasesP.statedIn] and not statement.snaks[aliasesP.referenceURL] then -- "stated in" was given but "reference URL" was not. -- get "Wikidata property" properties from the item in "stated in" -- if any of the returned properties of the external-id datatype is in statement.snaks, generate a link from it and use the link in the reference -- find the "Wikidata property" properties in the item from "stated in" wikidataPropertiesOfSource = mw.text.split(p._properties{p.flags.raw, aliasesP.wikidataProperty, [p.args.eid] = self.conf:getValue(statement.snaks[aliasesP.statedIn][1], true, false)}, ", ", true) for i, wikidataPropertyOfSource in pairs(wikidataPropertiesOfSource) do if statement.snaks[wikidataPropertyOfSource] and statement.snaks[wikidataPropertyOfSource][1].datatype == "external-id" then tempLink = self.conf:getValue(statement.snaks[wikidataPropertyOfSource][1], false, true) -- not raw, linked if mw.ustring.match(tempLink, "^%[%Z- %Z+%]$") then -- getValue returned a URL. additionalRefProperties[aliasesP.referenceURL] = mw.ustring.gsub(tempLink, "^%[(%Z-) %Z+%]$", "%1") -- the link is in wiki markup, so strip the square brackets and the display text statement.snaks[wikidataPropertyOfSource] = nil break end end end end -- don't include "subject named as", but use it as the title when "title" is not present but a URL is if statement.snaks[aliasesP.subjectNamedAs] then if not statement.snaks[aliasesP.title] and (statement.snaks[aliasesP.referenceURL] or additionalRefProperties[aliasesP.referenceURL]) then additionalRefProperties[aliasesP.title] = statement.snaks[aliasesP.subjectNamedAs][1].datavalue.value end statement.snaks[aliasesP.subjectNamedAs] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == aliasesP.author or i == aliasesP.authorNameString then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true elseif i == aliasesP.statedIn then -- Get "stated in" raw, as it is wanted (for Cite Q) even if it doesn't have a local language label. params[aliasesP.statedIn] = {self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true)} -- raw = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, self.linked and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else referenceEmpty = false if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then -- handle non-author properties (and author properties ("author" and "author name string"), if they don't use the same template parameter) if (i ~= aliasesP.author and i ~= aliasesP.authorNameString) or (i18n['cite'][j][aliasesP.author] ~= i18n['cite'][j][aliasesP.authorNameString]) then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- to avoid problems with non-author multiple parameters (if existent), the following old code is retained for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end -- handle "author" and "author name string" specially if they use the same template parameter elseif i == aliasesP.author or i == aliasesP.authorNameString then if params[aliasesP.author] ~= nil then numAuthorParameters = #params[aliasesP.author] else numAuthorParameters = 0 end if params[aliasesP.authorNameString] ~= nil then numAuthorNameStringParameters = #params[aliasesP.authorNameString] else numAuthorNameStringParameters = 0 end -- execute only if both "author" and "author name string" satisfy this condition: the property is both in params and in statement.snaks or it is neither in params nor in statement.snaks -- reason: parameters are added to params each iteration of the loop, not before the loop if ((statement.snaks[aliasesP.author] == nil) == (numAuthorParameters == 0)) and ((statement.snaks[aliasesP.authorNameString] == nil) == (numAuthorNameStringParameters == 0)) then for k=1, numAuthorParameters + numAuthorNameStringParameters do if k <= numAuthorParameters then -- now handling the authors from the "author" property citeParams[j][i18n['cite'][j][aliasesP.author]..k] = label .. params[aliasesP.author][k] else -- now handling the authors from "author name string" citeParams[j][i18n['cite'][j][aliasesP.authorNameString]..k] = label .. params[aliasesP.authorNameString][k - numAuthorParameters] end end end end end else citeMismatch[j] = true end end end end end -- use additional properties for i in pairs(additionalRefProperties) do for j in pairs(citeParams) do if not citeMismatch[j] and i18n["cite"][j][i] then citeParams[j][i18n["cite"][j][i]] = additionalRefProperties[i] else citeMismatch[j] = true end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least "reference URL" is present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] then -- we need a processed "stated in" for this template citeParams['web'][i18n['cite']['web'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, false, self.linked, true) useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) if the citation couldn't be displayed using Cite web or Cite Q, but has properties other than the removed ones, throw an error elseif not referenceEmpty then value = "<span style=\"color:#dd3333\">" .. errorText("malformed-reference") .. "</span>" end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix, globalSiteId) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page, globalSiteId) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], false, args[p.args.globalSiteId]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true, args[p.args.globalSiteId]) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p 5fwn1sr79pjmu0sp9hvxy0aeb10hvca 803554 803553 2024-06-23T09:33:45Z en>Janhrach 0 Update from sandbox – refs containing Wikimedia import URL, Inferred from or Imported from are ignored as a whole 803554 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. require("strict") local p = {} local arg = ... local i18n local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18n = require(title .. "/i18n").init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date", globalSiteId = "globalSiteId" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", authorNameString = "P2093", publisher = "P123", importedFrom = "P143", wikimediaImportURL = "P4656", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903", subjectNamedAs = "P1810", wikidataProperty = "P1687", publishedIn = "P1433" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables local function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end local function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) -- explicitly give base 10 to prevent error if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local prefix, title= "", nil if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id end prefix, title = "d:Special:EntityPage/", label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) -- XXX: should use fallback labels? end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id end end label = mw.text.nowiki(label) -- escape raw label text so it cannot be wikitext markup if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^%+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://geohack.toolforge.org/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local key, citeWeb, citeQ, label local params = {} local citeParams = {['web'] = {}, ['q'] = {}} local citeMismatch = {} local useCite = nil local useParams = nil local value = "" local ref = {} local referenceEmpty = true -- will be set to false if at least one parameter is left unremoved local numAuthorParameters = 0 local numAuthorNameStringParameters = 0 local tempLink local additionalRefProperties = {} -- will hold properties of the reference which are not in statement.snaks, namely backup title from "subject named as" and link from an external ID local wikidataPropertiesOfSource -- will contain "Wikidata property" properties of the item in stated in, if any local version = 8 -- increment this each time the below logic is changed to avoid conflict errors if statement.snaks then -- don't use bot-added references referencing Wikimedia projects or containing "inferred from" (such references are not usable on Wikipedia) if statement.snaks[aliasesP.importedFrom] or statement.snaks[aliasesP.wikimediaImportURL] or statement.snaks[aliasesP.inferredFrom] then statement.snaks = {} end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end if statement.snaks[aliasesP.statedIn] and not statement.snaks[aliasesP.referenceURL] then -- "stated in" was given but "reference URL" was not. -- get "Wikidata property" properties from the item in "stated in" -- if any of the returned properties of the external-id datatype is in statement.snaks, generate a link from it and use the link in the reference -- find the "Wikidata property" properties in the item from "stated in" wikidataPropertiesOfSource = mw.text.split(p._properties{p.flags.raw, aliasesP.wikidataProperty, [p.args.eid] = self.conf:getValue(statement.snaks[aliasesP.statedIn][1], true, false)}, ", ", true) for i, wikidataPropertyOfSource in pairs(wikidataPropertiesOfSource) do if statement.snaks[wikidataPropertyOfSource] and statement.snaks[wikidataPropertyOfSource][1].datatype == "external-id" then tempLink = self.conf:getValue(statement.snaks[wikidataPropertyOfSource][1], false, true) -- not raw, linked if mw.ustring.match(tempLink, "^%[%Z- %Z+%]$") then -- getValue returned a URL. additionalRefProperties[aliasesP.referenceURL] = mw.ustring.gsub(tempLink, "^%[(%Z-) %Z+%]$", "%1") -- the link is in wiki markup, so strip the square brackets and the display text statement.snaks[wikidataPropertyOfSource] = nil break end end end end -- don't include "subject named as", but use it as the title when "title" is not present but a URL is if statement.snaks[aliasesP.subjectNamedAs] then if not statement.snaks[aliasesP.title] and (statement.snaks[aliasesP.referenceURL] or additionalRefProperties[aliasesP.referenceURL]) then additionalRefProperties[aliasesP.title] = statement.snaks[aliasesP.subjectNamedAs][1].datavalue.value end statement.snaks[aliasesP.subjectNamedAs] = nil end -- retrieve all the parameters for i in pairs(statement.snaks) do label = "" -- multiple authors may be given if i == aliasesP.author or i == aliasesP.authorNameString then params[i] = self:getReferenceDetails(statement.snaks, i, false, self.linked, true) -- link = true/false, anyLang = true elseif i == aliasesP.statedIn then -- Get "stated in" raw, as it is wanted (for Cite Q) even if it doesn't have a local language label. params[aliasesP.statedIn] = {self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true)} -- raw = true else params[i] = {self:getReferenceDetail(statement.snaks, i, false, self.linked and (statement.snaks[i][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #params[i] == 0 then params[i] = nil else referenceEmpty = false if statement.snaks[i][1].datatype == 'external-id' then key = "external-id" label = self.conf:getLabel(i) if label ~= "" then label = label .. " " end else key = i end -- add the parameter to each matching type of citation for j in pairs(citeParams) do -- do so if there was no mismatch with a previous parameter if not citeMismatch[j] then -- check if this parameter is not mismatching itself if i18n['cite'][j][key] then -- continue if an option is available in the corresponding cite template if i18n['cite'][j][key] ~= "" then -- handle non-author properties (and author properties ("author" and "author name string"), if they don't use the same template parameter) if (i ~= aliasesP.author and i ~= aliasesP.authorNameString) or (i18n['cite'][j][aliasesP.author] ~= i18n['cite'][j][aliasesP.authorNameString]) then citeParams[j][i18n['cite'][j][key]] = label .. params[i][1] -- to avoid problems with non-author multiple parameters (if existent), the following old code is retained for k=2, #params[i] do citeParams[j][i18n['cite'][j][key]..k] = label .. params[i][k] end -- handle "author" and "author name string" specially if they use the same template parameter elseif i == aliasesP.author or i == aliasesP.authorNameString then if params[aliasesP.author] ~= nil then numAuthorParameters = #params[aliasesP.author] else numAuthorParameters = 0 end if params[aliasesP.authorNameString] ~= nil then numAuthorNameStringParameters = #params[aliasesP.authorNameString] else numAuthorNameStringParameters = 0 end -- execute only if both "author" and "author name string" satisfy this condition: the property is both in params and in statement.snaks or it is neither in params nor in statement.snaks -- reason: parameters are added to params each iteration of the loop, not before the loop if ((statement.snaks[aliasesP.author] == nil) == (numAuthorParameters == 0)) and ((statement.snaks[aliasesP.authorNameString] == nil) == (numAuthorNameStringParameters == 0)) then for k=1, numAuthorParameters + numAuthorNameStringParameters do if k <= numAuthorParameters then -- now handling the authors from the "author" property citeParams[j][i18n['cite'][j][aliasesP.author]..k] = label .. params[aliasesP.author][k] else -- now handling the authors from "author name string" citeParams[j][i18n['cite'][j][aliasesP.authorNameString]..k] = label .. params[aliasesP.authorNameString][k - numAuthorParameters] end end end end end else citeMismatch[j] = true end end end end end -- use additional properties for i in pairs(additionalRefProperties) do for j in pairs(citeParams) do if not citeMismatch[j] and i18n["cite"][j][i] then citeParams[j][i18n["cite"][j][i]] = additionalRefProperties[i] else citeMismatch[j] = true end end end -- get title of general template for citing web references citeWeb = split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- get title of template that expands stated-in references into citations citeQ = split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- (1) use the general template for citing web references if there is a match and if at least "reference URL" is present if citeWeb and not citeMismatch['web'] and citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] then -- we need a processed "stated in" for this template citeParams['web'][i18n['cite']['web'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, false, self.linked, true) useCite = citeWeb useParams = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeMismatch['q'] and citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then useCite = citeQ useParams = citeParams['q'] end if useCite and useParams then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(useParams) do value = value .. "|" .. i .. "=" .. v end value = "{{" .. useCite .. value .. "}}" else value = mw.getCurrentFrame():expandTemplate{title=useCite, args=useParams} end -- (3) if the citation couldn't be displayed using Cite web or Cite Q, but has properties other than the removed ones, throw an error elseif not referenceEmpty then value = "<span style=\"color:#dd3333\">" .. errorText("malformed-reference") .. "</span>" end if value ~= "" then value = {value} -- create one value object if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later value.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end ref = {value} -- wrap the value object in an array end end return ref end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix, globalSiteId) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page, globalSiteId) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], false, args[p.args.globalSiteId]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true, args[p.args.globalSiteId]) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p okmlxad0hk58sn1cgnll0y9v28zq1o7 803555 803554 2024-08-09T09:49:12Z en>Janhrach 0 Update from sandbox: clearer algorithm and better variable names in getReference 803555 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. require("strict") local p = {} local arg = ... local i18n local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18n = require(title .. "/i18n").init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date", globalSiteId = "globalSiteId" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", authorNameString = "P2093", publisher = "P123", importedFrom = "P143", wikimediaImportURL = "P4656", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903", subjectNamedAs = "P1810", wikidataProperty = "P1687", publishedIn = "P1433" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables local function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end local function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) -- explicitly give base 10 to prevent error if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local prefix, title= "", nil if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id end prefix, title = "d:Special:EntityPage/", label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) -- XXX: should use fallback labels? end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id end end label = mw.text.nowiki(label) -- escape raw label text so it cannot be wikitext markup if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^%+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://geohack.toolforge.org/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) -- title of general template for citing web references local citeWeb = split(mw.wikibase.getSitelink(aliasesQ.citeWeb) or "", ":")[2] -- split off namespace from front -- title of template that expands stated-in references into citations local citeQ = split(mw.wikibase.getSitelink(aliasesQ.citeQ) or "", ":")[2] -- split off namespace from front -- will hold rendered properties from statement.snaks local processedProperties = {} -- will hold properties of the reference which are not directly from statement.snaks, -- Namely, these are a backup title from "subject named as" and a URL generated from an external ID. local additionalProcessedProperties = {} -- for each citation type, there will be an associative array that associates lists of rendered properties -- to citation-template parameters local groupedProcessedProperties = {['web'] = {}, ['q'] = {}} -- like above, but only associates one rendered property to each parameter; if the above variable -- contains more strings for a parameter, the strings will be assigned to numbered params (e.g. "author1") local citeParams = {['web'] = {}, ['q'] = {}} local citeErrors = {} local referenceEmpty = true -- will be set to false if at least one parameter is left unremoved local version = 9 -- increment this each time the below logic is changed to avoid conflict errors if not statement.snaks then return {} end -- don't use bot-added references referencing Wikimedia projects or containing "inferred from" (such references are not usable on Wikipedia) if statement.snaks[aliasesP.importedFrom] or statement.snaks[aliasesP.wikimediaImportURL] or statement.snaks[aliasesP.inferredFrom] then return {} end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end if statement.snaks[aliasesP.statedIn] and not statement.snaks[aliasesP.referenceURL] then -- "stated in" was given but "reference URL" was not. -- get "Wikidata property" properties from the item in "stated in" -- if any of the returned properties of the external-id datatype is in statement.snaks, generate a link from it and use the link in the reference -- find the "Wikidata property" properties in the item from "stated in" local wikidataPropertiesOfSource = mw.text.split(p._properties{p.flags.raw, aliasesP.wikidataProperty, [p.args.eid] = self.conf:getValue(statement.snaks[aliasesP.statedIn][1], true, false)}, ", ", true) for i, wikidataPropertyOfSource in pairs(wikidataPropertiesOfSource) do if statement.snaks[wikidataPropertyOfSource] and statement.snaks[wikidataPropertyOfSource][1].datatype == "external-id" then local tempLink = self.conf:getValue(statement.snaks[wikidataPropertyOfSource][1], false, true) -- not raw, linked if mw.ustring.match(tempLink, "^%[%Z- %Z+%]$") then -- getValue returned a URL in square brackets. -- the link is in wiki markup, so strip the square brackets and the display text -- gsub also returns another, discarted value, therefore the result is assigned to tempLink first tempLink = mw.ustring.gsub(tempLink, "^%[(%Z-) %Z+%]$", "%1") additionalProcessedProperties[aliasesP.referenceURL] = {tempLink} statement.snaks[wikidataPropertyOfSource] = nil break end end end end -- don't include "subject named as", but use it as the title when "title" is not present but a URL is if statement.snaks[aliasesP.subjectNamedAs] then if not statement.snaks[aliasesP.title] and (statement.snaks[aliasesP.referenceURL] or additionalProcessedProperties[aliasesP.referenceURL]) then additionalProcessedProperties[aliasesP.title] = {statement.snaks[aliasesP.subjectNamedAs][1].datavalue.value} end statement.snaks[aliasesP.subjectNamedAs] = nil end -- fill processedProperties for refProperty in pairs(statement.snaks) do -- multiple authors may be given if refProperty == aliasesP.author or refProperty == aliasesP.authorNameString then processedProperties[refProperty] = self:getReferenceDetails(statement.snaks, refProperty, false, self.linked, true) -- link = true/false, anyLang = true elseif refProperty == aliasesP.statedIn then -- Get "stated in" raw, as it is wanted (for Cite Q) even if it doesn't have a local language label. processedProperties[aliasesP.statedIn] = {self:getReferenceDetail(statement.snaks, aliasesP.statedIn, true)} -- raw = true else processedProperties[refProperty] = {self:getReferenceDetail(statement.snaks, refProperty, false, self.linked and (statement.snaks[refProperty][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #processedProperties[refProperty] == 0 then processedProperties[refProperty] = nil end end -- fill groupedProcessedProperties for refProperty in pairs(statement.snaks) do if processedProperties[refProperty] ~= nil then local i18nKey local prefix = "" referenceEmpty = false if statement.snaks[refProperty][1].datatype == 'external-id' then i18nKey = "external-id" prefix = self.conf:getLabel(refProperty) if prefix ~= "" then prefix = prefix .. " " end else i18nKey = refProperty end -- add the parameter to each matching type of citation for citeType in pairs(groupedProcessedProperties) do repeat -- just a simple wrapper to emulate "continue" -- skip if there already have been errors if citeErrors[citeType] then break end local paramName = i18n['cite'][citeType][i18nKey] -- handle unknown properties in the reference if not paramName then citeErrors[citeType] = true break end if paramName == "" then break end referenceEmpty = false if not groupedProcessedProperties[citeType][paramName] then groupedProcessedProperties[citeType][paramName] = {} end for _, propertyValue in pairs(processedProperties[refProperty]) do table.insert(groupedProcessedProperties[citeType][paramName], propertyValue) end until true end end end -- handle additional properties for refProperty in pairs(additionalProcessedProperties) do for citeType in pairs(citeParams) do repeat -- skip if there already have been errors if citeErrors[citeType] then break end local paramName = i18n['cite'][citeType][refProperty] -- handle unknown properties in the reference if not paramName then citeErrors[citeType] = true break end if paramName == "" then break end referenceEmpty = false if not groupedProcessedProperties[citeType][paramName] then groupedProcessedProperties[citeType][paramName] = {} end for _, propertyValue in pairs(additionalProcessedProperties[refProperty]) do table.insert(groupedProcessedProperties[citeType][paramName], propertyValue) end until true end end -- fill citeParams for citeType in pairs(citeParams) do for paramName, paramValues in pairs(groupedProcessedProperties[citeType]) do if #paramValues == 1 then citeParams[citeType][paramName] = paramValues[1] else -- There is more than one value for this parameter - the values will -- go into separate numbered parameters (e.g. "author1", "author2") for paramNum, paramValue in pairs(paramValues) do citeParams[citeType][paramName .. paramNum] = paramValue end end end end -- handle missing mandatory parameters for the templates if not citeParams['web'][i18n['cite']['web'][aliasesP.referenceURL]] then citeErrors['web'] = true end if not citeParams['q'][i18n['cite']['q'][aliasesP.statedIn]] then citeErrors['q'] = true end local templateToUse = nil local paramsToUse = nil -- choose the output template -- (1) use the general template for citing web references if there is a match and if at least "reference URL" is present if citeWeb and not citeErrors['web'] then -- we need a processed "stated in" for this template citeParams['web'][i18n['cite']['web'][aliasesP.statedIn]] = self:getReferenceDetail(statement.snaks, aliasesP.statedIn, false, self.linked, true) templateToUse = citeWeb paramsToUse = citeParams['web'] -- (2) use the template that expands stated-in references into citations if there is a match and if at least "stated in" is present elseif citeQ and not citeErrors['q'] then templateToUse = citeQ paramsToUse = citeParams['q'] end -- fill refContent local refContent = "" if templateToUse and paramsToUse then -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(paramsToUse) do refContent = refContent .. "|" .. i .. "=" .. v end refContent = "{{" .. templateToUse .. refContent .. "}}" else refContent = mw.getCurrentFrame():expandTemplate{title=templateToUse, args=paramsToUse} end -- If the citation couldn't be displayed using Cite web or Cite Q, but is not empty (barring ignored propeties), throw an error elseif not referenceEmpty then refContent = "<span style=\"color:#dd3333\">" .. errorText("malformed-reference") .. "</span>" end -- wrap refContent local ref = {} if refContent ~= "" then ref = {refContent} if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later ref.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end return {ref} else return {} end end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix, globalSiteId) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page, globalSiteId) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], false, args[p.args.globalSiteId]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true, args[p.args.globalSiteId]) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p g1x7rh8ka7oxh7dyxs8jbfh9hxq1jhw 803556 803555 2024-08-26T09:41:13Z en>Janhrach 0 update from sandbox -- more configurability 803556 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. require("strict") local p = {} local arg = ... local i18n local i18nPath local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = arg end if not i18n then i18nPath = title .. "/i18n" i18n = require(i18nPath).init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date", globalSiteId = "globalSiteId" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", authorNameString = "P2093", publisher = "P123", importedFrom = "P143", wikimediaImportURL = "P4656", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903", subjectNamedAs = "P1810", wikidataProperty = "P1687", publishedIn = "P1433" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, param) local text = i18n["errors"][code] if param then text = mw.ustring.gsub(text, "$1", param) end return text end local function throwError(errorMessage, param) error(errorText(errorMessage, param)) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables local function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end local function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) -- explicitly give base 10 to prevent error if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end local function isValueInTable(searchedItem, inputTable) for _, item in pairs(inputTable) do if item == searchedItem then return true end end return false end local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local prefix, title= "", nil if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id end prefix, title = "d:Special:EntityPage/", label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) -- XXX: should use fallback labels? end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id end end label = mw.text.nowiki(label) -- escape raw label text so it cannot be wikitext markup if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^%+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://geohack.toolforge.org/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local citeParamMapping = i18n['cite']['param-mapping'] local citeConfig = i18n['cite']['config'] local citeTypes = i18n['cite']['output-types'] -- will hold rendered properties of the reference which are not directly from statement.snaks, -- Namely, these are a backup title from "subject named as" and a URL generated from an external ID. local additionalProcessedProperties = {} -- for each citation type, there will be an associative array that associates lists of rendered properties -- to citation-template parameters local groupedProcessedProperties = {} -- like above, but only associates one rendered property to each parameter; if the above variable -- contains more strings for a parameter, the strings will be assigned to numbered params (e.g. "author1") local citeParams = {} local citeErrors = {} local referenceEmpty = true -- will be set to false if at least one parameter is left unremoved local version = 10 -- increment this each time the below logic is changed to avoid conflict errors if not statement.snaks then return {} end -- don't use bot-added references referencing Wikimedia projects or containing "inferred from" (such references are not usable on Wikipedia) if statement.snaks[aliasesP.importedFrom] or statement.snaks[aliasesP.wikimediaImportURL] or statement.snaks[aliasesP.inferredFrom] then return {} end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end if statement.snaks[aliasesP.statedIn] and not statement.snaks[aliasesP.referenceURL] then -- "stated in" was given but "reference URL" was not. -- get "Wikidata property" properties from the item in "stated in" -- if any of the returned properties of the external-id datatype is in statement.snaks, generate a link from it and use the link in the reference -- find the "Wikidata property" properties in the item from "stated in" local wikidataPropertiesOfSource = mw.text.split(p._properties{p.flags.raw, aliasesP.wikidataProperty, [p.args.eid] = self.conf:getValue(statement.snaks[aliasesP.statedIn][1], true, false)}, ", ", true) for i, wikidataPropertyOfSource in pairs(wikidataPropertiesOfSource) do if statement.snaks[wikidataPropertyOfSource] and statement.snaks[wikidataPropertyOfSource][1].datatype == "external-id" then local tempLink = self:getReferenceDetail(statement.snaks, wikidataPropertyOfSource, false, true) -- not raw, linked if mw.ustring.match(tempLink, "^%[%Z- %Z+%]$") then -- getValue returned a URL in square brackets. -- the link is in wiki markup, so strip the square brackets and the display text -- gsub also returns another, discarted value, therefore the result is assigned to tempLink first tempLink = mw.ustring.gsub(tempLink, "^%[(%Z-) %Z+%]$", "%1") additionalProcessedProperties[aliasesP.referenceURL] = {tempLink} statement.snaks[wikidataPropertyOfSource] = nil break end end end end -- don't include "subject named as", but use it as the title when "title" is not present but a URL is if statement.snaks[aliasesP.subjectNamedAs] then if not statement.snaks[aliasesP.title] and (statement.snaks[aliasesP.referenceURL] or additionalProcessedProperties[aliasesP.referenceURL]) then additionalProcessedProperties[aliasesP.title] = {self:getReferenceDetail(statement.snaks, aliasesP.subjectNamedAs, false, false, true)} -- not raw, not linked, anyLang end statement.snaks[aliasesP.subjectNamedAs] = nil end -- initialize groupedProcessedProperties and citeParams for _, citeType in ipairs(citeTypes) do groupedProcessedProperties[citeType] = {} citeParams[citeType] = {} end -- fill groupedProcessedProperties for refProperty in pairs(statement.snaks) do -- add the parameter to each matching type of citation for _, citeType in ipairs(citeTypes) do repeat -- just a simple wrapper to emulate "continue" -- skip if there already have been errors if citeErrors[citeType] then break end -- set mappingKey and prefix local mappingKey local prefix = "" if statement.snaks[refProperty][1].datatype == 'external-id' then mappingKey = "external-id" prefix = self.conf:getLabel(refProperty) if prefix ~= "" then prefix = prefix .. " " end else mappingKey = refProperty end local paramName = citeParamMapping[citeType][mappingKey] -- skip properties with empty parameter name if paramName == "" then break end referenceEmpty = false -- handle unknown properties in the reference if not paramName then citeErrors[citeType] = true break end -- set processedProperty local processedProperty local raw = false -- if the value is wanted raw if isValueInTable(paramName, citeConfig[citeType]["raw-value-params"] or {}) then raw = true end if isValueInTable(paramName, citeConfig[citeType]["numbered-params"] or {}) then -- Multiple values may be given. processedProperty = self:getReferenceDetails(statement.snaks, refProperty, raw, self.linked, true) -- anyLang = true else -- If multiple values are given, all but the first one are discarted. processedProperty = {self:getReferenceDetail(statement.snaks, refProperty, raw, self.linked and (statement.snaks[refProperty][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #processedProperty == 0 then break end -- add an entry to groupedProcessedProperties if not groupedProcessedProperties[citeType][paramName] then groupedProcessedProperties[citeType][paramName] = {} end for _, propertyValue in pairs(processedProperty) do table.insert(groupedProcessedProperties[citeType][paramName], prefix .. propertyValue) end until true end end -- handle additional properties for refProperty in pairs(additionalProcessedProperties) do for _, citeType in ipairs(citeTypes) do repeat -- skip if there already have been errors if citeErrors[citeType] then break end local paramName = citeParamMapping[citeType][refProperty] -- handle unknown properties in the reference if not paramName then citeErrors[citeType] = true break end if paramName == "" then break end referenceEmpty = false if not groupedProcessedProperties[citeType][paramName] then groupedProcessedProperties[citeType][paramName] = {} end for _, propertyValue in pairs(additionalProcessedProperties[refProperty]) do table.insert(groupedProcessedProperties[citeType][paramName], propertyValue) end until true end end -- fill citeParams for _, citeType in ipairs(citeTypes) do for paramName, paramValues in pairs(groupedProcessedProperties[citeType]) do if #paramValues == 1 or not isValueInTable(paramName, citeConfig[citeType]["numbered-params"] or {}) then citeParams[citeType][paramName] = paramValues[1] else -- There is more than one value for this parameter - the values will -- go into separate numbered parameters (e.g. "author1", "author2") for paramNum, paramValue in pairs(paramValues) do citeParams[citeType][paramName .. paramNum] = paramValue end end end end -- handle missing mandatory parameters for the templates for _, citeType in ipairs(citeTypes) do for _, requiredCiteParam in pairs(citeConfig[citeType]["mandatory-params"] or {}) do if not citeParams[citeType][requiredCiteParam] then citeErrors[citeType] = true end end end -- handle missing templates in the citeConfig for _, citeType in ipairs(citeTypes) do if not citeConfig[citeType]["template"] then citeErrors[citeType] = true end end local citeTypeToUse = nil -- choose the output template for _, citeType in ipairs(citeTypes) do if not citeErrors[citeType] then citeTypeToUse = citeType break end end -- set refContent local refContent = "" if citeTypeToUse then local templateToUse = citeConfig[citeTypeToUse]["template"] local paramsToUse = citeParams[citeTypeToUse] -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(paramsToUse) do refContent = refContent .. "|" .. i .. "=" .. v end refContent = "{{" .. templateToUse .. refContent .. "}}" else xpcall( function () refContent = mw.getCurrentFrame():expandTemplate{title=templateToUse, args=paramsToUse} end, function () error(string.format(i18n["errors"]["no-such-reference-template"], templateToUse, i18nPath, citeTypeToUse)) end ) end -- If the citation couldn't be displayed using any template, but is not empty (barring ignored propeties), throw an error elseif not referenceEmpty then refContent = "<span style=\"color:#dd3333\">" .. errorText("malformed-reference") .. "</span>" end -- wrap refContent local ref = {} if refContent ~= "" then ref = {refContent} if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later ref.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['cite']['version']) + version) end return {ref} else return {} end end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix, globalSiteId) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page, globalSiteId) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], false, args[p.args.globalSiteId]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true, args[p.args.globalSiteId]) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p far7zxbx51awmo4yhoaeqglbj59u7kk 803557 803556 2024-09-02T16:58:49Z en>Janhrach 0 citation error message improvement and related internal tweaks 803557 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. require("strict") local p = {} local module_arg = ... local i18n local i18nPath local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = module_arg end if not i18n then i18nPath = title .. "/i18n" i18n = require(i18nPath).init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date", globalSiteId = "globalSiteId" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", authorNameString = "P2093", publisher = "P123", importedFrom = "P143", wikimediaImportURL = "P4656", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903", subjectNamedAs = "P1810", wikidataProperty = "P1687", publishedIn = "P1433" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, ...) local text = i18n["errors"][code] if arg then text = mw.ustring.format(text, unpack(arg)) end return text end local function throwError(errorMessage, ...) error(errorText(errorMessage, unpack(arg))) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables local function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end local function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) -- explicitly give base 10 to prevent error if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end local function isValueInTable(searchedItem, inputTable) for _, item in pairs(inputTable) do if item == searchedItem then return true end end return false end local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local prefix, title= "", nil if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id end prefix, title = "d:Special:EntityPage/", label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabelByLang(id, self.langCode) -- XXX: should use fallback labels? end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id end end label = mw.text.nowiki(label) -- escape raw label text so it cannot be wikitext markup if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^%+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://geohack.toolforge.org/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local citeParamMapping = i18n['cite']['param-mapping'] local citeConfig = i18n['cite']['config'] local citeTypes = i18n['cite']['output-types'] -- will hold rendered properties of the reference which are not directly from statement.snaks, -- Namely, these are a backup title from "subject named as" and a URL generated from an external ID. local additionalProcessedProperties = {} -- for each citation type, there will be an associative array that associates lists of rendered properties -- to citation-template parameters local groupedProcessedProperties = {} -- like above, but only associates one rendered property to each parameter; if the above variable -- contains more strings for a parameter, the strings will be assigned to numbered params (e.g. "author1") local citeParams = {} local citeErrors = {} local referenceEmpty = true -- will be set to false if at least one parameter is left unremoved local version = 11 -- increment this each time the below logic is changed to avoid conflict errors if not statement.snaks then return {} end -- don't use bot-added references referencing Wikimedia projects or containing "inferred from" (such references are not usable on Wikipedia) if statement.snaks[aliasesP.importedFrom] or statement.snaks[aliasesP.wikimediaImportURL] or statement.snaks[aliasesP.inferredFrom] then return {} end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end if statement.snaks[aliasesP.statedIn] and not statement.snaks[aliasesP.referenceURL] then -- "stated in" was given but "reference URL" was not. -- get "Wikidata property" properties from the item in "stated in" -- if any of the returned properties of the external-id datatype is in statement.snaks, generate a link from it and use the link in the reference -- find the "Wikidata property" properties in the item from "stated in" local wikidataPropertiesOfSource = mw.text.split(p._properties{p.flags.raw, aliasesP.wikidataProperty, [p.args.eid] = self.conf:getValue(statement.snaks[aliasesP.statedIn][1], true, false)}, ", ", true) for i, wikidataPropertyOfSource in pairs(wikidataPropertiesOfSource) do if statement.snaks[wikidataPropertyOfSource] and statement.snaks[wikidataPropertyOfSource][1].datatype == "external-id" then local tempLink = self:getReferenceDetail(statement.snaks, wikidataPropertyOfSource, false, true) -- not raw, linked if mw.ustring.match(tempLink, "^%[%Z- %Z+%]$") then -- getValue returned a URL in square brackets. -- the link is in wiki markup, so strip the square brackets and the display text -- gsub also returns another, discarted value, therefore the result is assigned to tempLink first tempLink = mw.ustring.gsub(tempLink, "^%[(%Z-) %Z+%]$", "%1") additionalProcessedProperties[aliasesP.referenceURL] = {tempLink} statement.snaks[wikidataPropertyOfSource] = nil break end end end end -- don't include "subject named as", but use it as the title when "title" is not present but a URL is if statement.snaks[aliasesP.subjectNamedAs] then if not statement.snaks[aliasesP.title] and (statement.snaks[aliasesP.referenceURL] or additionalProcessedProperties[aliasesP.referenceURL]) then additionalProcessedProperties[aliasesP.title] = {self:getReferenceDetail(statement.snaks, aliasesP.subjectNamedAs, false, false, true)} -- not raw, not linked, anyLang end statement.snaks[aliasesP.subjectNamedAs] = nil end -- initialize groupedProcessedProperties and citeParams for _, citeType in ipairs(citeTypes) do groupedProcessedProperties[citeType] = {} citeParams[citeType] = {} end -- fill groupedProcessedProperties for refProperty in pairs(statement.snaks) do -- add the parameter to each matching type of citation for _, citeType in ipairs(citeTypes) do repeat -- just a simple wrapper to emulate "continue" -- skip if there already have been errors if citeErrors[citeType] then break end -- set mappingKey and prefix local mappingKey local prefix = "" if statement.snaks[refProperty][1].datatype == 'external-id' then mappingKey = "external-id" prefix = self.conf:getLabel(refProperty) if prefix ~= "" then prefix = prefix .. " " end else mappingKey = refProperty end local paramName = citeParamMapping[citeType][mappingKey] -- skip properties with empty parameter name if paramName == "" then break end referenceEmpty = false -- handle unknown properties in the reference if not paramName then local error_message = errorText("unknown-property-in-ref", refProperty) assert(error_message) -- Should not be nil citeErrors[citeType] = error_message break end -- set processedProperty local processedProperty local raw = false -- if the value is wanted raw if isValueInTable(paramName, citeConfig[citeType]["raw-value-params"] or {}) then raw = true end if isValueInTable(paramName, citeConfig[citeType]["numbered-params"] or {}) then -- Multiple values may be given. processedProperty = self:getReferenceDetails(statement.snaks, refProperty, raw, self.linked, true) -- anyLang = true else -- If multiple values are given, all but the first suitable one are discarted. processedProperty = {self:getReferenceDetail(statement.snaks, refProperty, raw, self.linked and (statement.snaks[refProperty][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #processedProperty == 0 then break end -- add an entry to groupedProcessedProperties if not groupedProcessedProperties[citeType][paramName] then groupedProcessedProperties[citeType][paramName] = {} end for _, propertyValue in pairs(processedProperty) do table.insert(groupedProcessedProperties[citeType][paramName], prefix .. propertyValue) end until true end end -- handle additional properties for refProperty in pairs(additionalProcessedProperties) do for _, citeType in ipairs(citeTypes) do repeat -- skip if there already have been errors if citeErrors[citeType] then break end local paramName = citeParamMapping[citeType][refProperty] -- handle unknown properties in the reference if not paramName then -- Skip this additional property, but do not cause an error. break end if paramName == "" then break end referenceEmpty = false if not groupedProcessedProperties[citeType][paramName] then groupedProcessedProperties[citeType][paramName] = {} end for _, propertyValue in pairs(additionalProcessedProperties[refProperty]) do table.insert(groupedProcessedProperties[citeType][paramName], propertyValue) end until true end end -- fill citeParams for _, citeType in ipairs(citeTypes) do for paramName, paramValues in pairs(groupedProcessedProperties[citeType]) do if #paramValues == 1 or not isValueInTable(paramName, citeConfig[citeType]["numbered-params"] or {}) then citeParams[citeType][paramName] = paramValues[1] else -- There is more than one value for this parameter - the values will -- go into separate numbered parameters (e.g. "author1", "author2") for paramNum, paramValue in pairs(paramValues) do citeParams[citeType][paramName .. paramNum] = paramValue end end end end -- handle missing mandatory parameters for the templates for _, citeType in ipairs(citeTypes) do for _, requiredCiteParam in pairs(citeConfig[citeType]["mandatory-params"] or {}) do if not citeParams[citeType][requiredCiteParam] then -- The required param is not present. if citeErrors[citeType] then -- Do not override the previous error, if it exists. break end local error_message = errorText("missing-mandatory-param", requiredCiteParam) assert(error_message) -- Should not be nil citeErrors[citeType] = error_message end end end local citeTypeToUse = nil -- choose the output template for _, citeType in ipairs(citeTypes) do if not citeErrors[citeType] then citeTypeToUse = citeType break end end -- set refContent local refContent = "" if citeTypeToUse then local templateToUse = citeConfig[citeTypeToUse]["template"] local paramsToUse = citeParams[citeTypeToUse] if not templateToUse or templateToUse == "" then throwError("no-such-reference-template", tostring(templateToUse), i18nPath, citeTypeToUse) end -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(paramsToUse) do refContent = refContent .. "|" .. i .. "=" .. v end refContent = "{{" .. templateToUse .. refContent .. "}}" else xpcall( function () refContent = mw.getCurrentFrame():expandTemplate{title=templateToUse, args=paramsToUse} end, function () throwError("no-such-reference-template", templateToUse, i18nPath, citeTypeToUse) end ) end -- If the citation couldn't be displayed using any template, but is not empty (barring ignored propeties), throw an error. elseif not referenceEmpty then refContent = errorText("malformed-reference-header") for _, citeType in ipairs(citeTypes) do refContent = refContent .. errorText("template-failure-reason", citeConfig[citeType]["template"], citeErrors[citeType]) end refContent = refContent .. errorText("malformed-reference-footer") end -- wrap refContent local ref = {} if refContent ~= "" then ref = {refContent} if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later ref.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['version']) + version) end return {ref} else return {} end end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix, globalSiteId) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page, globalSiteId) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], false, args[p.args.globalSiteId]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true, args[p.args.globalSiteId]) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p 7tb3f3tkj144piju76l6sugrzm31r8m 803558 803557 2024-09-12T18:29:19Z en>Janhrach 0 Update from sandbox – use proper fallback for labels 803558 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. require("strict") local p = {} local module_arg = ... local i18n local i18nPath local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = module_arg end if not i18n then i18nPath = title .. "/i18n" i18n = require(i18nPath).init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date", globalSiteId = "globalSiteId" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", authorNameString = "P2093", publisher = "P123", importedFrom = "P143", wikimediaImportURL = "P4656", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903", subjectNamedAs = "P1810", wikidataProperty = "P1687", publishedIn = "P1433" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, ...) local text = i18n["errors"][code] if arg then text = mw.ustring.format(text, unpack(arg)) end return text end local function throwError(errorMessage, ...) error(errorText(errorMessage, unpack(arg))) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables local function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end local function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) -- explicitly give base 10 to prevent error if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end local function isValueInTable(searchedItem, inputTable) for _, item in pairs(inputTable) do if item == searchedItem then return true end end return false end local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local prefix, title= "", nil if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id end prefix, title = "d:Special:EntityPage/", label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabel(id) end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id end end label = mw.text.nowiki(label) -- escape raw label text so it cannot be wikitext markup if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^%+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://geohack.toolforge.org/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local citeParamMapping = i18n['cite']['param-mapping'] local citeConfig = i18n['cite']['config'] local citeTypes = i18n['cite']['output-types'] -- will hold rendered properties of the reference which are not directly from statement.snaks, -- Namely, these are a backup title from "subject named as" and a URL generated from an external ID. local additionalProcessedProperties = {} -- for each citation type, there will be an associative array that associates lists of rendered properties -- to citation-template parameters local groupedProcessedProperties = {} -- like above, but only associates one rendered property to each parameter; if the above variable -- contains more strings for a parameter, the strings will be assigned to numbered params (e.g. "author1") local citeParams = {} local citeErrors = {} local referenceEmpty = true -- will be set to false if at least one parameter is left unremoved local version = 11 -- increment this each time the below logic is changed to avoid conflict errors if not statement.snaks then return {} end -- don't use bot-added references referencing Wikimedia projects or containing "inferred from" (such references are not usable on Wikipedia) if statement.snaks[aliasesP.importedFrom] or statement.snaks[aliasesP.wikimediaImportURL] or statement.snaks[aliasesP.inferredFrom] then return {} end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end if statement.snaks[aliasesP.statedIn] and not statement.snaks[aliasesP.referenceURL] then -- "stated in" was given but "reference URL" was not. -- get "Wikidata property" properties from the item in "stated in" -- if any of the returned properties of the external-id datatype is in statement.snaks, generate a link from it and use the link in the reference -- find the "Wikidata property" properties in the item from "stated in" local wikidataPropertiesOfSource = mw.text.split(p._properties{p.flags.raw, aliasesP.wikidataProperty, [p.args.eid] = self.conf:getValue(statement.snaks[aliasesP.statedIn][1], true, false)}, ", ", true) for i, wikidataPropertyOfSource in pairs(wikidataPropertiesOfSource) do if statement.snaks[wikidataPropertyOfSource] and statement.snaks[wikidataPropertyOfSource][1].datatype == "external-id" then local tempLink = self:getReferenceDetail(statement.snaks, wikidataPropertyOfSource, false, true) -- not raw, linked if mw.ustring.match(tempLink, "^%[%Z- %Z+%]$") then -- getValue returned a URL in square brackets. -- the link is in wiki markup, so strip the square brackets and the display text -- gsub also returns another, discarted value, therefore the result is assigned to tempLink first tempLink = mw.ustring.gsub(tempLink, "^%[(%Z-) %Z+%]$", "%1") additionalProcessedProperties[aliasesP.referenceURL] = {tempLink} statement.snaks[wikidataPropertyOfSource] = nil break end end end end -- don't include "subject named as", but use it as the title when "title" is not present but a URL is if statement.snaks[aliasesP.subjectNamedAs] then if not statement.snaks[aliasesP.title] and (statement.snaks[aliasesP.referenceURL] or additionalProcessedProperties[aliasesP.referenceURL]) then additionalProcessedProperties[aliasesP.title] = {self:getReferenceDetail(statement.snaks, aliasesP.subjectNamedAs, false, false, true)} -- not raw, not linked, anyLang end statement.snaks[aliasesP.subjectNamedAs] = nil end -- initialize groupedProcessedProperties and citeParams for _, citeType in ipairs(citeTypes) do groupedProcessedProperties[citeType] = {} citeParams[citeType] = {} end -- fill groupedProcessedProperties for refProperty in pairs(statement.snaks) do -- add the parameter to each matching type of citation for _, citeType in ipairs(citeTypes) do repeat -- just a simple wrapper to emulate "continue" -- skip if there already have been errors if citeErrors[citeType] then break end -- set mappingKey and prefix local mappingKey local prefix = "" if statement.snaks[refProperty][1].datatype == 'external-id' then mappingKey = "external-id" prefix = self.conf:getLabel(refProperty) if prefix ~= "" then prefix = prefix .. " " end else mappingKey = refProperty end local paramName = citeParamMapping[citeType][mappingKey] -- skip properties with empty parameter name if paramName == "" then break end referenceEmpty = false -- handle unknown properties in the reference if not paramName then local error_message = errorText("unknown-property-in-ref", refProperty) assert(error_message) -- Should not be nil citeErrors[citeType] = error_message break end -- set processedProperty local processedProperty local raw = false -- if the value is wanted raw if isValueInTable(paramName, citeConfig[citeType]["raw-value-params"] or {}) then raw = true end if isValueInTable(paramName, citeConfig[citeType]["numbered-params"] or {}) then -- Multiple values may be given. processedProperty = self:getReferenceDetails(statement.snaks, refProperty, raw, self.linked, true) -- anyLang = true else -- If multiple values are given, all but the first suitable one are discarted. processedProperty = {self:getReferenceDetail(statement.snaks, refProperty, raw, self.linked and (statement.snaks[refProperty][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #processedProperty == 0 then break end -- add an entry to groupedProcessedProperties if not groupedProcessedProperties[citeType][paramName] then groupedProcessedProperties[citeType][paramName] = {} end for _, propertyValue in pairs(processedProperty) do table.insert(groupedProcessedProperties[citeType][paramName], prefix .. propertyValue) end until true end end -- handle additional properties for refProperty in pairs(additionalProcessedProperties) do for _, citeType in ipairs(citeTypes) do repeat -- skip if there already have been errors if citeErrors[citeType] then break end local paramName = citeParamMapping[citeType][refProperty] -- handle unknown properties in the reference if not paramName then -- Skip this additional property, but do not cause an error. break end if paramName == "" then break end referenceEmpty = false if not groupedProcessedProperties[citeType][paramName] then groupedProcessedProperties[citeType][paramName] = {} end for _, propertyValue in pairs(additionalProcessedProperties[refProperty]) do table.insert(groupedProcessedProperties[citeType][paramName], propertyValue) end until true end end -- fill citeParams for _, citeType in ipairs(citeTypes) do for paramName, paramValues in pairs(groupedProcessedProperties[citeType]) do if #paramValues == 1 or not isValueInTable(paramName, citeConfig[citeType]["numbered-params"] or {}) then citeParams[citeType][paramName] = paramValues[1] else -- There is more than one value for this parameter - the values will -- go into separate numbered parameters (e.g. "author1", "author2") for paramNum, paramValue in pairs(paramValues) do citeParams[citeType][paramName .. paramNum] = paramValue end end end end -- handle missing mandatory parameters for the templates for _, citeType in ipairs(citeTypes) do for _, requiredCiteParam in pairs(citeConfig[citeType]["mandatory-params"] or {}) do if not citeParams[citeType][requiredCiteParam] then -- The required param is not present. if citeErrors[citeType] then -- Do not override the previous error, if it exists. break end local error_message = errorText("missing-mandatory-param", requiredCiteParam) assert(error_message) -- Should not be nil citeErrors[citeType] = error_message end end end local citeTypeToUse = nil -- choose the output template for _, citeType in ipairs(citeTypes) do if not citeErrors[citeType] then citeTypeToUse = citeType break end end -- set refContent local refContent = "" if citeTypeToUse then local templateToUse = citeConfig[citeTypeToUse]["template"] local paramsToUse = citeParams[citeTypeToUse] if not templateToUse or templateToUse == "" then throwError("no-such-reference-template", tostring(templateToUse), i18nPath, citeTypeToUse) end -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(paramsToUse) do refContent = refContent .. "|" .. i .. "=" .. v end refContent = "{{" .. templateToUse .. refContent .. "}}" else xpcall( function () refContent = mw.getCurrentFrame():expandTemplate{title=templateToUse, args=paramsToUse} end, function () throwError("no-such-reference-template", templateToUse, i18nPath, citeTypeToUse) end ) end -- If the citation couldn't be displayed using any template, but is not empty (barring ignored propeties), throw an error. elseif not referenceEmpty then refContent = errorText("malformed-reference-header") for _, citeType in ipairs(citeTypes) do refContent = refContent .. errorText("template-failure-reason", citeConfig[citeType]["template"], citeErrors[citeType]) end refContent = refContent .. errorText("malformed-reference-footer") end -- wrap refContent local ref = {} if refContent ~= "" then ref = {refContent} if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later ref.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['version']) + version) end return {ref} else return {} end end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix, globalSiteId) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page, globalSiteId) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], false, args[p.args.globalSiteId]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true, args[p.args.globalSiteId]) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p klnosw6qk6m6vbnazowivije405gt0c 803559 803558 2024-10-05T13:15:17Z en>Janhrach 0 update – add publication place to aliasesQ 803559 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. require("strict") local p = {} local module_arg = ... local i18n local i18nPath local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = module_arg end if not i18n then i18nPath = title .. "/i18n" i18n = require(i18nPath).init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date", globalSiteId = "globalSiteId" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", authorNameString = "P2093", publisher = "P123", importedFrom = "P143", wikimediaImportURL = "P4656", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903", subjectNamedAs = "P1810", wikidataProperty = "P1687", publishedIn = "P1433", placeOfPublication = "P291" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, ...) local text = i18n["errors"][code] if arg then text = mw.ustring.format(text, unpack(arg)) end return text end local function throwError(errorMessage, ...) error(errorText(errorMessage, unpack(arg))) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables local function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end local function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) -- explicitly give base 10 to prevent error if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end local function isValueInTable(searchedItem, inputTable) for _, item in pairs(inputTable) do if item == searchedItem then return true end end return false end local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local prefix, title= "", nil if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id end prefix, title = "d:Special:EntityPage/", label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabel(id) end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id end end label = mw.text.nowiki(label) -- escape raw label text so it cannot be wikitext markup if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^%+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://geohack.toolforge.org/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local citeParamMapping = i18n['cite']['param-mapping'] local citeConfig = i18n['cite']['config'] local citeTypes = i18n['cite']['output-types'] -- will hold rendered properties of the reference which are not directly from statement.snaks, -- Namely, these are a backup title from "subject named as" and a URL generated from an external ID. local additionalProcessedProperties = {} -- for each citation type, there will be an associative array that associates lists of rendered properties -- to citation-template parameters local groupedProcessedProperties = {} -- like above, but only associates one rendered property to each parameter; if the above variable -- contains more strings for a parameter, the strings will be assigned to numbered params (e.g. "author1") local citeParams = {} local citeErrors = {} local referenceEmpty = true -- will be set to false if at least one parameter is left unremoved local version = 11 -- increment this each time the below logic is changed to avoid conflict errors if not statement.snaks then return {} end -- don't use bot-added references referencing Wikimedia projects or containing "inferred from" (such references are not usable on Wikipedia) if statement.snaks[aliasesP.importedFrom] or statement.snaks[aliasesP.wikimediaImportURL] or statement.snaks[aliasesP.inferredFrom] then return {} end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end if statement.snaks[aliasesP.statedIn] and not statement.snaks[aliasesP.referenceURL] then -- "stated in" was given but "reference URL" was not. -- get "Wikidata property" properties from the item in "stated in" -- if any of the returned properties of the external-id datatype is in statement.snaks, generate a link from it and use the link in the reference -- find the "Wikidata property" properties in the item from "stated in" local wikidataPropertiesOfSource = mw.text.split(p._properties{p.flags.raw, aliasesP.wikidataProperty, [p.args.eid] = self.conf:getValue(statement.snaks[aliasesP.statedIn][1], true, false)}, ", ", true) for i, wikidataPropertyOfSource in pairs(wikidataPropertiesOfSource) do if statement.snaks[wikidataPropertyOfSource] and statement.snaks[wikidataPropertyOfSource][1].datatype == "external-id" then local tempLink = self:getReferenceDetail(statement.snaks, wikidataPropertyOfSource, false, true) -- not raw, linked if mw.ustring.match(tempLink, "^%[%Z- %Z+%]$") then -- getValue returned a URL in square brackets. -- the link is in wiki markup, so strip the square brackets and the display text -- gsub also returns another, discarted value, therefore the result is assigned to tempLink first tempLink = mw.ustring.gsub(tempLink, "^%[(%Z-) %Z+%]$", "%1") additionalProcessedProperties[aliasesP.referenceURL] = {tempLink} statement.snaks[wikidataPropertyOfSource] = nil break end end end end -- don't include "subject named as", but use it as the title when "title" is not present but a URL is if statement.snaks[aliasesP.subjectNamedAs] then if not statement.snaks[aliasesP.title] and (statement.snaks[aliasesP.referenceURL] or additionalProcessedProperties[aliasesP.referenceURL]) then additionalProcessedProperties[aliasesP.title] = {self:getReferenceDetail(statement.snaks, aliasesP.subjectNamedAs, false, false, true)} -- not raw, not linked, anyLang end statement.snaks[aliasesP.subjectNamedAs] = nil end -- initialize groupedProcessedProperties and citeParams for _, citeType in ipairs(citeTypes) do groupedProcessedProperties[citeType] = {} citeParams[citeType] = {} end -- fill groupedProcessedProperties for refProperty in pairs(statement.snaks) do -- add the parameter to each matching type of citation for _, citeType in ipairs(citeTypes) do repeat -- just a simple wrapper to emulate "continue" -- skip if there already have been errors if citeErrors[citeType] then break end -- set mappingKey and prefix local mappingKey local prefix = "" if statement.snaks[refProperty][1].datatype == 'external-id' then mappingKey = "external-id" prefix = self.conf:getLabel(refProperty) if prefix ~= "" then prefix = prefix .. " " end else mappingKey = refProperty end local paramName = citeParamMapping[citeType][mappingKey] -- skip properties with empty parameter name if paramName == "" then break end referenceEmpty = false -- handle unknown properties in the reference if not paramName then local error_message = errorText("unknown-property-in-ref", refProperty) assert(error_message) -- Should not be nil citeErrors[citeType] = error_message break end -- set processedProperty local processedProperty local raw = false -- if the value is wanted raw if isValueInTable(paramName, citeConfig[citeType]["raw-value-params"] or {}) then raw = true end if isValueInTable(paramName, citeConfig[citeType]["numbered-params"] or {}) then -- Multiple values may be given. processedProperty = self:getReferenceDetails(statement.snaks, refProperty, raw, self.linked, true) -- anyLang = true else -- If multiple values are given, all but the first suitable one are discarted. processedProperty = {self:getReferenceDetail(statement.snaks, refProperty, raw, self.linked and (statement.snaks[refProperty][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #processedProperty == 0 then break end -- add an entry to groupedProcessedProperties if not groupedProcessedProperties[citeType][paramName] then groupedProcessedProperties[citeType][paramName] = {} end for _, propertyValue in pairs(processedProperty) do table.insert(groupedProcessedProperties[citeType][paramName], prefix .. propertyValue) end until true end end -- handle additional properties for refProperty in pairs(additionalProcessedProperties) do for _, citeType in ipairs(citeTypes) do repeat -- skip if there already have been errors if citeErrors[citeType] then break end local paramName = citeParamMapping[citeType][refProperty] -- handle unknown properties in the reference if not paramName then -- Skip this additional property, but do not cause an error. break end if paramName == "" then break end referenceEmpty = false if not groupedProcessedProperties[citeType][paramName] then groupedProcessedProperties[citeType][paramName] = {} end for _, propertyValue in pairs(additionalProcessedProperties[refProperty]) do table.insert(groupedProcessedProperties[citeType][paramName], propertyValue) end until true end end -- fill citeParams for _, citeType in ipairs(citeTypes) do for paramName, paramValues in pairs(groupedProcessedProperties[citeType]) do if #paramValues == 1 or not isValueInTable(paramName, citeConfig[citeType]["numbered-params"] or {}) then citeParams[citeType][paramName] = paramValues[1] else -- There is more than one value for this parameter - the values will -- go into separate numbered parameters (e.g. "author1", "author2") for paramNum, paramValue in pairs(paramValues) do citeParams[citeType][paramName .. paramNum] = paramValue end end end end -- handle missing mandatory parameters for the templates for _, citeType in ipairs(citeTypes) do for _, requiredCiteParam in pairs(citeConfig[citeType]["mandatory-params"] or {}) do if not citeParams[citeType][requiredCiteParam] then -- The required param is not present. if citeErrors[citeType] then -- Do not override the previous error, if it exists. break end local error_message = errorText("missing-mandatory-param", requiredCiteParam) assert(error_message) -- Should not be nil citeErrors[citeType] = error_message end end end local citeTypeToUse = nil -- choose the output template for _, citeType in ipairs(citeTypes) do if not citeErrors[citeType] then citeTypeToUse = citeType break end end -- set refContent local refContent = "" if citeTypeToUse then local templateToUse = citeConfig[citeTypeToUse]["template"] local paramsToUse = citeParams[citeTypeToUse] if not templateToUse or templateToUse == "" then throwError("no-such-reference-template", tostring(templateToUse), i18nPath, citeTypeToUse) end -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(paramsToUse) do refContent = refContent .. "|" .. i .. "=" .. v end refContent = "{{" .. templateToUse .. refContent .. "}}" else xpcall( function () refContent = mw.getCurrentFrame():expandTemplate{title=templateToUse, args=paramsToUse} end, function () throwError("no-such-reference-template", templateToUse, i18nPath, citeTypeToUse) end ) end -- If the citation couldn't be displayed using any template, but is not empty (barring ignored propeties), throw an error. elseif not referenceEmpty then refContent = errorText("malformed-reference-header") for _, citeType in ipairs(citeTypes) do refContent = refContent .. errorText("template-failure-reason", citeConfig[citeType]["template"], citeErrors[citeType]) end refContent = refContent .. errorText("malformed-reference-footer") end -- wrap refContent local ref = {} if refContent ~= "" then ref = {refContent} if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later ref.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['version']) + version) end return {ref} else return {} end end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix, globalSiteId) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page, globalSiteId) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], false, args[p.args.globalSiteId]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true, args[p.args.globalSiteId]) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p dfwb49viphxxyyv065y7kt387kgposz 803560 803559 2024-10-05T14:22:38Z en>Janhrach 0 Undid revision [[Special:Diff/1249540315|1249540315]] by [[Special:Contributions/Janhrach|Janhrach]] ([[User talk:Janhrach|talk]]) 803560 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. require("strict") local p = {} local module_arg = ... local i18n local i18nPath local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = module_arg end if not i18n then i18nPath = title .. "/i18n" i18n = require(i18nPath).init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date", globalSiteId = "globalSiteId" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", authorNameString = "P2093", publisher = "P123", importedFrom = "P143", wikimediaImportURL = "P4656", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903", subjectNamedAs = "P1810", wikidataProperty = "P1687", publishedIn = "P1433" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, ...) local text = i18n["errors"][code] if arg then text = mw.ustring.format(text, unpack(arg)) end return text end local function throwError(errorMessage, ...) error(errorText(errorMessage, unpack(arg))) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables local function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end local function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) -- explicitly give base 10 to prevent error if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end local function isValueInTable(searchedItem, inputTable) for _, item in pairs(inputTable) do if item == searchedItem then return true end end return false end local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local prefix, title= "", nil if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id end prefix, title = "d:Special:EntityPage/", label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabel(id) end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id end end label = mw.text.nowiki(label) -- escape raw label text so it cannot be wikitext markup if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^%+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://geohack.toolforge.org/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local citeParamMapping = i18n['cite']['param-mapping'] local citeConfig = i18n['cite']['config'] local citeTypes = i18n['cite']['output-types'] -- will hold rendered properties of the reference which are not directly from statement.snaks, -- Namely, these are a backup title from "subject named as" and a URL generated from an external ID. local additionalProcessedProperties = {} -- for each citation type, there will be an associative array that associates lists of rendered properties -- to citation-template parameters local groupedProcessedProperties = {} -- like above, but only associates one rendered property to each parameter; if the above variable -- contains more strings for a parameter, the strings will be assigned to numbered params (e.g. "author1") local citeParams = {} local citeErrors = {} local referenceEmpty = true -- will be set to false if at least one parameter is left unremoved local version = 11 -- increment this each time the below logic is changed to avoid conflict errors if not statement.snaks then return {} end -- don't use bot-added references referencing Wikimedia projects or containing "inferred from" (such references are not usable on Wikipedia) if statement.snaks[aliasesP.importedFrom] or statement.snaks[aliasesP.wikimediaImportURL] or statement.snaks[aliasesP.inferredFrom] then return {} end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end if statement.snaks[aliasesP.statedIn] and not statement.snaks[aliasesP.referenceURL] then -- "stated in" was given but "reference URL" was not. -- get "Wikidata property" properties from the item in "stated in" -- if any of the returned properties of the external-id datatype is in statement.snaks, generate a link from it and use the link in the reference -- find the "Wikidata property" properties in the item from "stated in" local wikidataPropertiesOfSource = mw.text.split(p._properties{p.flags.raw, aliasesP.wikidataProperty, [p.args.eid] = self.conf:getValue(statement.snaks[aliasesP.statedIn][1], true, false)}, ", ", true) for i, wikidataPropertyOfSource in pairs(wikidataPropertiesOfSource) do if statement.snaks[wikidataPropertyOfSource] and statement.snaks[wikidataPropertyOfSource][1].datatype == "external-id" then local tempLink = self:getReferenceDetail(statement.snaks, wikidataPropertyOfSource, false, true) -- not raw, linked if mw.ustring.match(tempLink, "^%[%Z- %Z+%]$") then -- getValue returned a URL in square brackets. -- the link is in wiki markup, so strip the square brackets and the display text -- gsub also returns another, discarted value, therefore the result is assigned to tempLink first tempLink = mw.ustring.gsub(tempLink, "^%[(%Z-) %Z+%]$", "%1") additionalProcessedProperties[aliasesP.referenceURL] = {tempLink} statement.snaks[wikidataPropertyOfSource] = nil break end end end end -- don't include "subject named as", but use it as the title when "title" is not present but a URL is if statement.snaks[aliasesP.subjectNamedAs] then if not statement.snaks[aliasesP.title] and (statement.snaks[aliasesP.referenceURL] or additionalProcessedProperties[aliasesP.referenceURL]) then additionalProcessedProperties[aliasesP.title] = {self:getReferenceDetail(statement.snaks, aliasesP.subjectNamedAs, false, false, true)} -- not raw, not linked, anyLang end statement.snaks[aliasesP.subjectNamedAs] = nil end -- initialize groupedProcessedProperties and citeParams for _, citeType in ipairs(citeTypes) do groupedProcessedProperties[citeType] = {} citeParams[citeType] = {} end -- fill groupedProcessedProperties for refProperty in pairs(statement.snaks) do -- add the parameter to each matching type of citation for _, citeType in ipairs(citeTypes) do repeat -- just a simple wrapper to emulate "continue" -- skip if there already have been errors if citeErrors[citeType] then break end -- set mappingKey and prefix local mappingKey local prefix = "" if statement.snaks[refProperty][1].datatype == 'external-id' then mappingKey = "external-id" prefix = self.conf:getLabel(refProperty) if prefix ~= "" then prefix = prefix .. " " end else mappingKey = refProperty end local paramName = citeParamMapping[citeType][mappingKey] -- skip properties with empty parameter name if paramName == "" then break end referenceEmpty = false -- handle unknown properties in the reference if not paramName then local error_message = errorText("unknown-property-in-ref", refProperty) assert(error_message) -- Should not be nil citeErrors[citeType] = error_message break end -- set processedProperty local processedProperty local raw = false -- if the value is wanted raw if isValueInTable(paramName, citeConfig[citeType]["raw-value-params"] or {}) then raw = true end if isValueInTable(paramName, citeConfig[citeType]["numbered-params"] or {}) then -- Multiple values may be given. processedProperty = self:getReferenceDetails(statement.snaks, refProperty, raw, self.linked, true) -- anyLang = true else -- If multiple values are given, all but the first suitable one are discarted. processedProperty = {self:getReferenceDetail(statement.snaks, refProperty, raw, self.linked and (statement.snaks[refProperty][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #processedProperty == 0 then break end -- add an entry to groupedProcessedProperties if not groupedProcessedProperties[citeType][paramName] then groupedProcessedProperties[citeType][paramName] = {} end for _, propertyValue in pairs(processedProperty) do table.insert(groupedProcessedProperties[citeType][paramName], prefix .. propertyValue) end until true end end -- handle additional properties for refProperty in pairs(additionalProcessedProperties) do for _, citeType in ipairs(citeTypes) do repeat -- skip if there already have been errors if citeErrors[citeType] then break end local paramName = citeParamMapping[citeType][refProperty] -- handle unknown properties in the reference if not paramName then -- Skip this additional property, but do not cause an error. break end if paramName == "" then break end referenceEmpty = false if not groupedProcessedProperties[citeType][paramName] then groupedProcessedProperties[citeType][paramName] = {} end for _, propertyValue in pairs(additionalProcessedProperties[refProperty]) do table.insert(groupedProcessedProperties[citeType][paramName], propertyValue) end until true end end -- fill citeParams for _, citeType in ipairs(citeTypes) do for paramName, paramValues in pairs(groupedProcessedProperties[citeType]) do if #paramValues == 1 or not isValueInTable(paramName, citeConfig[citeType]["numbered-params"] or {}) then citeParams[citeType][paramName] = paramValues[1] else -- There is more than one value for this parameter - the values will -- go into separate numbered parameters (e.g. "author1", "author2") for paramNum, paramValue in pairs(paramValues) do citeParams[citeType][paramName .. paramNum] = paramValue end end end end -- handle missing mandatory parameters for the templates for _, citeType in ipairs(citeTypes) do for _, requiredCiteParam in pairs(citeConfig[citeType]["mandatory-params"] or {}) do if not citeParams[citeType][requiredCiteParam] then -- The required param is not present. if citeErrors[citeType] then -- Do not override the previous error, if it exists. break end local error_message = errorText("missing-mandatory-param", requiredCiteParam) assert(error_message) -- Should not be nil citeErrors[citeType] = error_message end end end local citeTypeToUse = nil -- choose the output template for _, citeType in ipairs(citeTypes) do if not citeErrors[citeType] then citeTypeToUse = citeType break end end -- set refContent local refContent = "" if citeTypeToUse then local templateToUse = citeConfig[citeTypeToUse]["template"] local paramsToUse = citeParams[citeTypeToUse] if not templateToUse or templateToUse == "" then throwError("no-such-reference-template", tostring(templateToUse), i18nPath, citeTypeToUse) end -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(paramsToUse) do refContent = refContent .. "|" .. i .. "=" .. v end refContent = "{{" .. templateToUse .. refContent .. "}}" else xpcall( function () refContent = mw.getCurrentFrame():expandTemplate{title=templateToUse, args=paramsToUse} end, function () throwError("no-such-reference-template", templateToUse, i18nPath, citeTypeToUse) end ) end -- If the citation couldn't be displayed using any template, but is not empty (barring ignored propeties), throw an error. elseif not referenceEmpty then refContent = errorText("malformed-reference-header") for _, citeType in ipairs(citeTypes) do refContent = refContent .. errorText("template-failure-reason", citeConfig[citeType]["template"], citeErrors[citeType]) end refContent = refContent .. errorText("malformed-reference-footer") end -- wrap refContent local ref = {} if refContent ~= "" then ref = {refContent} if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later ref.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['version']) + version) end return {ref} else return {} end end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) local valuesArray, refHash -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix, globalSiteId) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page, globalSiteId) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], false, args[p.args.globalSiteId]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true, args[p.args.globalSiteId]) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p klnosw6qk6m6vbnazowivije405gt0c 803561 803560 2024-11-20T21:00:06Z en>Uzume 0 remove unused local "refHash" and narrow lifetime/scope of "valuesArray" 803561 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. require("strict") local p = {} local module_arg = ... local i18n local i18nPath local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = module_arg end if not i18n then i18nPath = title .. "/i18n" i18n = require(i18nPath).init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date", globalSiteId = "globalSiteId" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", authorNameString = "P2093", publisher = "P123", importedFrom = "P143", wikimediaImportURL = "P4656", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903", subjectNamedAs = "P1810", wikidataProperty = "P1687", publishedIn = "P1433" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, ...) local text = i18n["errors"][code] if arg then text = mw.ustring.format(text, unpack(arg)) end return text end local function throwError(errorMessage, ...) error(errorText(errorMessage, unpack(arg))) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables local function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end local function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) -- explicitly give base 10 to prevent error if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end local function isValueInTable(searchedItem, inputTable) for _, item in pairs(inputTable) do if item == searchedItem then return true end end return false end local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local prefix, title= "", nil if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id end prefix, title = "d:Special:EntityPage/", label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabel(id) end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id end end label = mw.text.nowiki(label) -- escape raw label text so it cannot be wikitext markup if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^%+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://geohack.toolforge.org/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local citeParamMapping = i18n['cite']['param-mapping'] local citeConfig = i18n['cite']['config'] local citeTypes = i18n['cite']['output-types'] -- will hold rendered properties of the reference which are not directly from statement.snaks, -- Namely, these are a backup title from "subject named as" and a URL generated from an external ID. local additionalProcessedProperties = {} -- for each citation type, there will be an associative array that associates lists of rendered properties -- to citation-template parameters local groupedProcessedProperties = {} -- like above, but only associates one rendered property to each parameter; if the above variable -- contains more strings for a parameter, the strings will be assigned to numbered params (e.g. "author1") local citeParams = {} local citeErrors = {} local referenceEmpty = true -- will be set to false if at least one parameter is left unremoved local version = 11 -- increment this each time the below logic is changed to avoid conflict errors if not statement.snaks then return {} end -- don't use bot-added references referencing Wikimedia projects or containing "inferred from" (such references are not usable on Wikipedia) if statement.snaks[aliasesP.importedFrom] or statement.snaks[aliasesP.wikimediaImportURL] or statement.snaks[aliasesP.inferredFrom] then return {} end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end if statement.snaks[aliasesP.statedIn] and not statement.snaks[aliasesP.referenceURL] then -- "stated in" was given but "reference URL" was not. -- get "Wikidata property" properties from the item in "stated in" -- if any of the returned properties of the external-id datatype is in statement.snaks, generate a link from it and use the link in the reference -- find the "Wikidata property" properties in the item from "stated in" local wikidataPropertiesOfSource = mw.text.split(p._properties{p.flags.raw, aliasesP.wikidataProperty, [p.args.eid] = self.conf:getValue(statement.snaks[aliasesP.statedIn][1], true, false)}, ", ", true) for i, wikidataPropertyOfSource in pairs(wikidataPropertiesOfSource) do if statement.snaks[wikidataPropertyOfSource] and statement.snaks[wikidataPropertyOfSource][1].datatype == "external-id" then local tempLink = self:getReferenceDetail(statement.snaks, wikidataPropertyOfSource, false, true) -- not raw, linked if mw.ustring.match(tempLink, "^%[%Z- %Z+%]$") then -- getValue returned a URL in square brackets. -- the link is in wiki markup, so strip the square brackets and the display text -- gsub also returns another, discarted value, therefore the result is assigned to tempLink first tempLink = mw.ustring.gsub(tempLink, "^%[(%Z-) %Z+%]$", "%1") additionalProcessedProperties[aliasesP.referenceURL] = {tempLink} statement.snaks[wikidataPropertyOfSource] = nil break end end end end -- don't include "subject named as", but use it as the title when "title" is not present but a URL is if statement.snaks[aliasesP.subjectNamedAs] then if not statement.snaks[aliasesP.title] and (statement.snaks[aliasesP.referenceURL] or additionalProcessedProperties[aliasesP.referenceURL]) then additionalProcessedProperties[aliasesP.title] = {self:getReferenceDetail(statement.snaks, aliasesP.subjectNamedAs, false, false, true)} -- not raw, not linked, anyLang end statement.snaks[aliasesP.subjectNamedAs] = nil end -- initialize groupedProcessedProperties and citeParams for _, citeType in ipairs(citeTypes) do groupedProcessedProperties[citeType] = {} citeParams[citeType] = {} end -- fill groupedProcessedProperties for refProperty in pairs(statement.snaks) do -- add the parameter to each matching type of citation for _, citeType in ipairs(citeTypes) do repeat -- just a simple wrapper to emulate "continue" -- skip if there already have been errors if citeErrors[citeType] then break end -- set mappingKey and prefix local mappingKey local prefix = "" if statement.snaks[refProperty][1].datatype == 'external-id' then mappingKey = "external-id" prefix = self.conf:getLabel(refProperty) if prefix ~= "" then prefix = prefix .. " " end else mappingKey = refProperty end local paramName = citeParamMapping[citeType][mappingKey] -- skip properties with empty parameter name if paramName == "" then break end referenceEmpty = false -- handle unknown properties in the reference if not paramName then local error_message = errorText("unknown-property-in-ref", refProperty) assert(error_message) -- Should not be nil citeErrors[citeType] = error_message break end -- set processedProperty local processedProperty local raw = false -- if the value is wanted raw if isValueInTable(paramName, citeConfig[citeType]["raw-value-params"] or {}) then raw = true end if isValueInTable(paramName, citeConfig[citeType]["numbered-params"] or {}) then -- Multiple values may be given. processedProperty = self:getReferenceDetails(statement.snaks, refProperty, raw, self.linked, true) -- anyLang = true else -- If multiple values are given, all but the first suitable one are discarted. processedProperty = {self:getReferenceDetail(statement.snaks, refProperty, raw, self.linked and (statement.snaks[refProperty][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #processedProperty == 0 then break end -- add an entry to groupedProcessedProperties if not groupedProcessedProperties[citeType][paramName] then groupedProcessedProperties[citeType][paramName] = {} end for _, propertyValue in pairs(processedProperty) do table.insert(groupedProcessedProperties[citeType][paramName], prefix .. propertyValue) end until true end end -- handle additional properties for refProperty in pairs(additionalProcessedProperties) do for _, citeType in ipairs(citeTypes) do repeat -- skip if there already have been errors if citeErrors[citeType] then break end local paramName = citeParamMapping[citeType][refProperty] -- handle unknown properties in the reference if not paramName then -- Skip this additional property, but do not cause an error. break end if paramName == "" then break end referenceEmpty = false if not groupedProcessedProperties[citeType][paramName] then groupedProcessedProperties[citeType][paramName] = {} end for _, propertyValue in pairs(additionalProcessedProperties[refProperty]) do table.insert(groupedProcessedProperties[citeType][paramName], propertyValue) end until true end end -- fill citeParams for _, citeType in ipairs(citeTypes) do for paramName, paramValues in pairs(groupedProcessedProperties[citeType]) do if #paramValues == 1 or not isValueInTable(paramName, citeConfig[citeType]["numbered-params"] or {}) then citeParams[citeType][paramName] = paramValues[1] else -- There is more than one value for this parameter - the values will -- go into separate numbered parameters (e.g. "author1", "author2") for paramNum, paramValue in pairs(paramValues) do citeParams[citeType][paramName .. paramNum] = paramValue end end end end -- handle missing mandatory parameters for the templates for _, citeType in ipairs(citeTypes) do for _, requiredCiteParam in pairs(citeConfig[citeType]["mandatory-params"] or {}) do if not citeParams[citeType][requiredCiteParam] then -- The required param is not present. if citeErrors[citeType] then -- Do not override the previous error, if it exists. break end local error_message = errorText("missing-mandatory-param", requiredCiteParam) assert(error_message) -- Should not be nil citeErrors[citeType] = error_message end end end local citeTypeToUse = nil -- choose the output template for _, citeType in ipairs(citeTypes) do if not citeErrors[citeType] then citeTypeToUse = citeType break end end -- set refContent local refContent = "" if citeTypeToUse then local templateToUse = citeConfig[citeTypeToUse]["template"] local paramsToUse = citeParams[citeTypeToUse] if not templateToUse or templateToUse == "" then throwError("no-such-reference-template", tostring(templateToUse), i18nPath, citeTypeToUse) end -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(paramsToUse) do refContent = refContent .. "|" .. i .. "=" .. v end refContent = "{{" .. templateToUse .. refContent .. "}}" else xpcall( function () refContent = mw.getCurrentFrame():expandTemplate{title=templateToUse, args=paramsToUse} end, function () throwError("no-such-reference-template", templateToUse, i18nPath, citeTypeToUse) end ) end -- If the citation couldn't be displayed using any template, but is not empty (barring ignored propeties), throw an error. elseif not referenceEmpty then refContent = errorText("malformed-reference-header") for _, citeType in ipairs(citeTypes) do refContent = refContent .. errorText("template-failure-reason", citeConfig[citeType]["template"], citeErrors[citeType]) end refContent = refContent .. errorText("malformed-reference-footer") end -- wrap refContent local ref = {} if refContent ~= "" then ref = {refContent} if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later ref.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['version']) + version) end return {ref} else return {} end end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then local valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix, globalSiteId) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page, globalSiteId) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], false, args[p.args.globalSiteId]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true, args[p.args.globalSiteId]) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p fw0wujzhw9loowczj4s2zaqoeawla5r 803562 803561 2025-07-22T19:53:47Z en>Janhrach 0 update from sandbox 803562 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. require("strict") local p = {} local module_arg = ... local i18n local i18nPath local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = module_arg end if not i18n then i18nPath = title .. "/i18n" i18n = require(i18nPath).init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date", globalSiteId = "globalSiteId" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", authorNameString = "P2093", publisher = "P123", importedFrom = "P143", wikimediaImportURL = "P4656", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903", subjectNamedAs = "P1810", wikidataProperty = "P1687", publishedIn = "P1433", lastUpdate = "P5017" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, ...) local text = i18n["errors"][code] if arg then text = mw.ustring.format(text, unpack(arg)) end return text end local function throwError(errorMessage, ...) error(errorText(errorMessage, unpack(arg))) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables local function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end local function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) -- explicitly give base 10 to prevent error if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end local function isValueInTable(searchedItem, inputTable) for _, item in pairs(inputTable) do if item == searchedItem then return true end end return false end local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local prefix, title= "", nil if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id end prefix, title = "d:Special:EntityPage/", label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabel(id) end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id end end label = mw.text.nowiki(label) -- escape raw label text so it cannot be wikitext markup if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^%+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://geohack.toolforge.org/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local citeParamMapping = i18n['cite']['param-mapping'] local citeConfig = i18n['cite']['config'] local citeTypes = i18n['cite']['output-types'] -- will hold rendered properties of the reference which are not directly from statement.snaks, -- Namely, is URL generated from an external ID. local additionalProcessedProperties = {} -- for each citation type, there will be an associative array that associates lists of rendered properties -- to citation-template parameters local candidateParams = {} -- like above, but only associates one rendered property to each parameter; if the above variable -- contains more strings for a parameter, the strings will be assigned to numbered params (e.g. "author1") local citeParams = {} local citeErrors = {} local referenceEmpty = true -- will be set to false if at least one parameter is left unremoved local version = 12 -- increment this each time the below logic is changed to avoid conflict errors if not statement.snaks then return {} end -- don't use bot-added references referencing Wikimedia projects or containing "inferred from" (such references are not usable on Wikipedia) if statement.snaks[aliasesP.importedFrom] or statement.snaks[aliasesP.wikimediaImportURL] or statement.snaks[aliasesP.inferredFrom] then return {} end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end if statement.snaks[aliasesP.statedIn] and not statement.snaks[aliasesP.referenceURL] then -- "stated in" was given but "reference URL" was not. -- get "Wikidata property" properties from the item in "stated in" -- if any of the returned properties of the external-id datatype is in statement.snaks, generate a link from it and use the link in the reference -- find the "Wikidata property" properties in the item from "stated in" local wikidataPropertiesOfSource = mw.text.split(p._properties{p.flags.raw, aliasesP.wikidataProperty, [p.args.eid] = self.conf:getValue(statement.snaks[aliasesP.statedIn][1], true, false)}, ", ", true) for i, wikidataPropertyOfSource in pairs(wikidataPropertiesOfSource) do if statement.snaks[wikidataPropertyOfSource] and statement.snaks[wikidataPropertyOfSource][1].datatype == "external-id" then local tempLink = self:getReferenceDetail(statement.snaks, wikidataPropertyOfSource, false, true) -- not raw, linked if mw.ustring.match(tempLink, "^%[%Z- %Z+%]$") then -- getValue returned a URL in square brackets. -- the link is in wiki markup, so strip the square brackets and the display text -- gsub also returns another, discarted value, therefore the result is assigned to tempLink first tempLink = mw.ustring.gsub(tempLink, "^%[(%Z-) %Z+%]$", "%1") additionalProcessedProperties[aliasesP.referenceURL] = {tempLink} statement.snaks[wikidataPropertyOfSource] = nil break end end end end -- initialize candidateParams and citeParams for _, citeType in ipairs(citeTypes) do candidateParams[citeType] = {} citeParams[citeType] = {} end -- fill candidateParams for _, citeType in ipairs(citeTypes) do -- This will contain value--priority pairs for each param name. local candidateValuesAndPriorities = {} -- fill candidateValuesAndPriorities for refProperty in pairs(statement.snaks) do if citeErrors[citeType] then break end repeat -- just a simple wrapper to emulate "continue" -- set mappingKey and prefix local mappingKey local prefix = "" if statement.snaks[refProperty][1].datatype == 'external-id' then mappingKey = "external-id" prefix = self.conf:getLabel(refProperty) if prefix ~= "" then prefix = prefix .. " " end else mappingKey = refProperty end local paramName = citeParamMapping[citeType][mappingKey] -- skip properties with empty parameter name if paramName == "" then break -- skip this property for this value of citeType end -- handle unknown properties in the reference if not paramName then referenceEmpty = false local error_message = errorText("unknown-property-in-ref", refProperty) assert(error_message) -- Should not be nil citeErrors[citeType] = error_message break end -- set processedProperty local processedProperty local raw = false -- if the value is wanted raw if isValueInTable(paramName, citeConfig[citeType]["raw-value-params"] or {}) then raw = true end if isValueInTable(paramName, citeConfig[citeType]["numbered-params"] or {}) then -- Multiple values may be given. processedProperty = self:getReferenceDetails(statement.snaks, refProperty, raw, self.linked, true) -- anyLang = true else -- If multiple values are given, all but the first suitable one are discarted. processedProperty = {self:getReferenceDetail(statement.snaks, refProperty, raw, self.linked and (statement.snaks[refProperty][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #processedProperty == 0 then break end referenceEmpty = false -- add an empty entry to candidateValuesAndPriorities, if there isn't one already if not candidateValuesAndPriorities[paramName] then candidateValuesAndPriorities[paramName] = {} end -- find the priority of refProperty local thisPropertyPriority = -1 local thisParamPrioritization = citeConfig[citeType]["prioritization"][paramName] if thisParamPrioritization then for i_priority, i_property in ipairs(thisParamPrioritization) do if i_property == refProperty then thisPropertyPriority = i_priority end end end for _, propertyValue in pairs(processedProperty) do table.insert( candidateValuesAndPriorities[paramName], {prefix .. propertyValue, thisPropertyPriority} ) end until true end -- fill candidateParams[citeType] if not citeErrors[citeType] then local compareValuePriorities = function(pair1, pair2) if pair1[2] == -1 and pair2[2] ~= -1 then return false end if pair1[2] ~= -1 and pair2[2] == -1 then return true end return pair1[2] < pair2[2] end -- fill candidateParams[citeType][paramName] for each used param for paramName, _ in pairs(candidateValuesAndPriorities) do table.sort(candidateValuesAndPriorities[paramName], compareValuePriorities) candidateParams[citeType][paramName] = {} for _, valuePriorityPair in ipairs(candidateValuesAndPriorities[paramName]) do table.insert(candidateParams[citeType][paramName], valuePriorityPair[1]) end end end end -- handle additional properties for refProperty in pairs(additionalProcessedProperties) do for _, citeType in ipairs(citeTypes) do repeat -- skip if there already have been errors if citeErrors[citeType] then break end local paramName = citeParamMapping[citeType][refProperty] -- handle unknown properties in the reference if not paramName then -- Skip this additional property, but do not cause an error. break end if paramName == "" then break end referenceEmpty = false if not candidateParams[citeType][paramName] then candidateParams[citeType][paramName] = {} end for _, propertyValue in pairs(additionalProcessedProperties[refProperty]) do table.insert(candidateParams[citeType][paramName], propertyValue) end until true end end -- fill citeParams for _, citeType in ipairs(citeTypes) do for paramName, paramValues in pairs(candidateParams[citeType]) do if #paramValues == 1 or not isValueInTable(paramName, citeConfig[citeType]["numbered-params"] or {}) then citeParams[citeType][paramName] = paramValues[1] else -- There is more than one value for this parameter - the values will -- go into separate numbered parameters (e.g. "author1", "author2") for paramNum, paramValue in pairs(paramValues) do citeParams[citeType][paramName .. paramNum] = paramValue end end end end -- handle missing mandatory parameters for the templates for _, citeType in ipairs(citeTypes) do for _, requiredCiteParam in pairs(citeConfig[citeType]["mandatory-params"] or {}) do if not citeParams[citeType][requiredCiteParam] then -- The required param is not present. if citeErrors[citeType] then -- Do not override the previous error, if it exists. break end local error_message = errorText("missing-mandatory-param", requiredCiteParam) assert(error_message) -- Should not be nil citeErrors[citeType] = error_message end end end local citeTypeToUse = nil -- choose the output template for _, citeType in ipairs(citeTypes) do if not citeErrors[citeType] then citeTypeToUse = citeType break end end -- set refContent local refContent = "" if citeTypeToUse then local templateToUse = citeConfig[citeTypeToUse]["template"] local paramsToUse = citeParams[citeTypeToUse] if not templateToUse or templateToUse == "" then throwError("no-such-reference-template", tostring(templateToUse), i18nPath, citeTypeToUse) end -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(paramsToUse) do refContent = refContent .. "|" .. i .. "=" .. v end refContent = "{{" .. templateToUse .. refContent .. "}}" else xpcall( function () refContent = mw.getCurrentFrame():expandTemplate{title=templateToUse, args=paramsToUse} end, function () throwError("no-such-reference-template", templateToUse, i18nPath, citeTypeToUse) end ) end -- If the citation couldn't be displayed using any template, but is not empty (barring ignored propeties), throw an error. elseif not referenceEmpty then refContent = errorText("malformed-reference-header") for _, citeType in ipairs(citeTypes) do refContent = refContent .. errorText("template-failure-reason", citeConfig[citeType]["template"], citeErrors[citeType]) end refContent = refContent .. errorText("malformed-reference-footer") end -- wrap refContent local ref = {} if refContent ~= "" then ref = {refContent} if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later ref.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['version']) + version) end return {ref} else return {} end end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then local valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix, globalSiteId) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page, globalSiteId) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], false, args[p.args.globalSiteId]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true, args[p.args.globalSiteId]) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p j5a6l03tjwodgrvfnv3lb4x5up93wlv 803563 799218 2026-07-30T00:16:24Z SM7 3953 55 revisions imported from [[:en:Module:Wd]] 799218 Scribunto text/plain -- Original module located at [[:en:Module:Wd]] and [[:en:Module:Wd/i18n]]. require("strict") local p = {} local module_arg = ... local i18n local i18nPath local function loadI18n(aliasesP, frame) local title if frame then -- current module invoked by page/template, get its title from frame title = frame:getTitle() else -- current module included by other module, get its title from ... title = module_arg end if not i18n then i18nPath = title .. "/i18n" i18n = require(i18nPath).init(aliasesP) end end p.claimCommands = { property = "property", properties = "properties", qualifier = "qualifier", qualifiers = "qualifiers", reference = "reference", references = "references" } p.generalCommands = { label = "label", title = "title", description = "description", alias = "alias", aliases = "aliases", badge = "badge", badges = "badges" } p.flags = { linked = "linked", short = "short", raw = "raw", multilanguage = "multilanguage", unit = "unit", ------------- preferred = "preferred", normal = "normal", deprecated = "deprecated", best = "best", future = "future", current = "current", former = "former", edit = "edit", editAtEnd = "edit@end", mdy = "mdy", single = "single", sourced = "sourced" } p.args = { eid = "eid", page = "page", date = "date", globalSiteId = "globalSiteId" } local aliasesP = { coord = "P625", ----------------------- image = "P18", author = "P50", authorNameString = "P2093", publisher = "P123", importedFrom = "P143", wikimediaImportURL = "P4656", statedIn = "P248", pages = "P304", language = "P407", hasPart = "P527", publicationDate = "P577", startTime = "P580", endTime = "P582", chapter = "P792", retrieved = "P813", referenceURL = "P854", sectionVerseOrParagraph = "P958", archiveURL = "P1065", title = "P1476", formatterURL = "P1630", quote = "P1683", shortName = "P1813", definingFormula = "P2534", archiveDate = "P2960", inferredFrom = "P3452", typeOfReference = "P3865", column = "P3903", subjectNamedAs = "P1810", wikidataProperty = "P1687", publishedIn = "P1433", lastUpdate = "P5017" } local aliasesQ = { percentage = "Q11229", prolepticJulianCalendar = "Q1985786", citeWeb = "Q5637226", citeQ = "Q22321052" } local parameters = { property = "%p", qualifier = "%q", reference = "%r", alias = "%a", badge = "%b", separator = "%s", general = "%x" } local formats = { property = "%p[%s][%r]", qualifier = "%q[%s][%r]", reference = "%r", propertyWithQualifier = "%p[ <span style=\"font-size:85\\%\">(%q)</span>][%s][%r]", alias = "%a[%s]", badge = "%b[%s]" } local hookNames = { -- {level_1, level_2} [parameters.property] = {"getProperty"}, [parameters.reference] = {"getReferences", "getReference"}, [parameters.qualifier] = {"getAllQualifiers"}, [parameters.qualifier.."\\d"] = {"getQualifiers", "getQualifier"}, [parameters.alias] = {"getAlias"}, [parameters.badge] = {"getBadge"} } -- default value objects, should NOT be mutated but instead copied local defaultSeparators = { ["sep"] = {" "}, ["sep%s"] = {","}, ["sep%q"] = {"; "}, ["sep%q\\d"] = {", "}, ["sep%r"] = nil, -- none ["punc"] = nil -- none } local rankTable = { ["preferred"] = 1, ["normal"] = 2, ["deprecated"] = 3 } local function replaceAlias(id) if aliasesP[id] then id = aliasesP[id] end return id end local function errorText(code, ...) local text = i18n["errors"][code] if arg then text = mw.ustring.format(text, unpack(arg)) end return text end local function throwError(errorMessage, ...) error(errorText(errorMessage, unpack(arg))) end local function replaceDecimalMark(num) return mw.ustring.gsub(num, "[.]", i18n['numeric']['decimal-mark'], 1) end local function padZeros(num, numDigits) local numZeros local negative = false if num < 0 then negative = true num = num * -1 end num = tostring(num) numZeros = numDigits - num:len() for _ = 1, numZeros do num = "0"..num end if negative then num = "-"..num end return num end local function replaceSpecialChar(chr) if chr == '_' then -- replace underscores with spaces return ' ' else return chr end end local function replaceSpecialChars(str) local chr local esc = false local strOut = "" for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then esc = true else strOut = strOut .. replaceSpecialChar(chr) end else strOut = strOut .. chr esc = false end end return strOut end local function buildWikilink(target, label) if not label or target == label then return "[[" .. target .. "]]" else return "[[" .. target .. "|" .. label .. "]]" end end -- used to make frame.args mutable, to replace #frame.args (which is always 0) -- with the actual amount and to simply copy tables local function copyTable(tIn) if not tIn then return nil end local tOut = {} for i, v in pairs(tIn) do tOut[i] = v end return tOut end -- used to merge output arrays together; -- note that it currently mutates the first input array local function mergeArrays(a1, a2) for i = 1, #a2 do a1[#a1 + 1] = a2[i] end return a1 end local function split(str, del) local out = {} local i, j = str:find(del) if i and j then out[1] = str:sub(1, i - 1) out[2] = str:sub(j + 1) else out[1] = str end return out end local function parseWikidataURL(url) local id if url:match('^http[s]?://') then id = split(url, "Q") if id[2] then return "Q" .. id[2] end end return nil end local function parseDate(dateStr, precision) precision = precision or "d" local i, j, index, ptr local parts = {nil, nil, nil} if dateStr == nil then return parts[1], parts[2], parts[3] -- year, month, day end -- 'T' for snak values, '/' for outputs with '/Julian' attached i, j = dateStr:find("[T/]") if i then dateStr = dateStr:sub(1, i-1) end local from = 1 if dateStr:sub(1,1) == "-" then -- this is a negative number, look further ahead from = 2 end index = 1 ptr = 1 i, j = dateStr:find("-", from) if i then -- year parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) -- explicitly give base 10 to prevent error if parts[index] == -0 then parts[index] = tonumber("0") -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead end if precision == "y" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 i, j = dateStr:find("-", ptr) if i then -- month parts[index] = tonumber(dateStr:sub(ptr, i-1), 10) if precision == "m" then -- we're done return parts[1], parts[2], parts[3] -- year, month, day end index = index + 1 ptr = i + 1 end end if dateStr:sub(ptr) ~= "" then -- day if we have month, month if we have year, or year parts[index] = tonumber(dateStr:sub(ptr), 10) end return parts[1], parts[2], parts[3] -- year, month, day end local function datePrecedesDate(aY, aM, aD, bY, bM, bD) if aY == nil or bY == nil then return nil end aM = aM or 1 aD = aD or 1 bM = bM or 1 bD = bD or 1 if aY < bY then return true end if aY > bY then return false end if aM < bM then return true end if aM > bM then return false end if aD < bD then return true end return false end local function getHookName(param, index) if hookNames[param] then return hookNames[param][index] elseif param:len() > 2 then return hookNames[param:sub(1, 2).."\\d"][index] else return nil end end local function alwaysTrue() return true end -- The following function parses a format string. -- -- The example below shows how a parsed string is structured in memory. -- Variables other than 'str' and 'child' are left out for clarity's sake. -- -- Example: -- "A %p B [%s[%q1]] C [%r] D" -- -- Structure: -- [ -- { -- str = "A " -- }, -- { -- str = "%p" -- }, -- { -- str = " B ", -- child = -- [ -- { -- str = "%s", -- child = -- [ -- { -- str = "%q1" -- } -- ] -- } -- ] -- }, -- { -- str = " C ", -- child = -- [ -- { -- str = "%r" -- } -- ] -- }, -- { -- str = " D" -- } -- ] -- local function parseFormat(str) local chr, esc, param, root, cur, prev, new local params = {} local function newObject(array) local obj = {} -- new object obj.str = "" array[#array + 1] = obj -- array{object} obj.parent = array return obj end local function endParam() if param > 0 then if cur.str ~= "" then cur.str = "%"..cur.str cur.param = true params[cur.str] = true cur.parent.req[cur.str] = true prev = cur cur = newObject(cur.parent) end param = 0 end end root = {} -- array root.req = {} cur = newObject(root) prev = nil esc = false param = 0 for i = 1, #str do chr = str:sub(i,i) if not esc then if chr == '\\' then endParam() esc = true elseif chr == '%' then endParam() if cur.str ~= "" then cur = newObject(cur.parent) end param = 2 elseif chr == '[' then endParam() if prev and cur.str == "" then table.remove(cur.parent) cur = prev end cur.child = {} -- new array cur.child.req = {} cur.child.parent = cur cur = newObject(cur.child) elseif chr == ']' then endParam() if cur.parent.parent then new = newObject(cur.parent.parent.parent) if cur.str == "" then table.remove(cur.parent) end cur = new end else if param > 1 then param = param - 1 elseif param == 1 then if not chr:match('%d') then endParam() end end cur.str = cur.str .. replaceSpecialChar(chr) end else cur.str = cur.str .. chr esc = false end prev = nil end endParam() -- make sure that at least one required parameter has been defined if not next(root.req) then throwError("missing-required-parameter") end -- make sure that the separator parameter "%s" is not amongst the required parameters if root.req[parameters.separator] then throwError("extra-required-parameter", parameters.separator) end return root, params end local function sortOnRank(claims) local rankPos local ranks = {{}, {}, {}, {}} -- preferred, normal, deprecated, (default) local sorted = {} for _, v in ipairs(claims) do rankPos = rankTable[v.rank] or 4 ranks[rankPos][#ranks[rankPos] + 1] = v end sorted = ranks[1] sorted = mergeArrays(sorted, ranks[2]) sorted = mergeArrays(sorted, ranks[3]) return sorted end local function isValueInTable(searchedItem, inputTable) for _, item in pairs(inputTable) do if item == searchedItem then return true end end return false end local Config = {} -- allows for recursive calls function Config:new() local cfg = {} setmetatable(cfg, self) self.__index = self cfg.separators = { -- single value objects wrapped in arrays so that we can pass by reference ["sep"] = {copyTable(defaultSeparators["sep"])}, ["sep%s"] = {copyTable(defaultSeparators["sep%s"])}, ["sep%q"] = {copyTable(defaultSeparators["sep%q"])}, ["sep%r"] = {copyTable(defaultSeparators["sep%r"])}, ["punc"] = {copyTable(defaultSeparators["punc"])} } cfg.entity = nil cfg.entityID = nil cfg.propertyID = nil cfg.propertyValue = nil cfg.qualifierIDs = {} cfg.qualifierIDsAndValues = {} cfg.bestRank = true cfg.ranks = {true, true, false} -- preferred = true, normal = true, deprecated = false cfg.foundRank = #cfg.ranks cfg.flagBest = false cfg.flagRank = false cfg.periods = {true, true, true} -- future = true, current = true, former = true cfg.flagPeriod = false cfg.atDate = {parseDate(os.date('!%Y-%m-%d'))} -- today as {year, month, day} cfg.mdyDate = false cfg.singleClaim = false cfg.sourcedOnly = false cfg.editable = false cfg.editAtEnd = false cfg.inSitelinks = false cfg.langCode = mw.language.getContentLanguage().code cfg.langName = mw.language.fetchLanguageName(cfg.langCode, cfg.langCode) cfg.langObj = mw.language.new(cfg.langCode) cfg.siteID = mw.wikibase.getGlobalSiteId() cfg.states = {} cfg.states.qualifiersCount = 0 cfg.curState = nil cfg.prefetchedRefs = nil return cfg end local State = {} function State:new(cfg, type) local stt = {} setmetatable(stt, self) self.__index = self stt.conf = cfg stt.type = type stt.results = {} stt.parsedFormat = {} stt.separator = {} stt.movSeparator = {} stt.puncMark = {} stt.linked = false stt.rawValue = false stt.shortName = false stt.anyLanguage = false stt.unitOnly = false stt.singleValue = false return stt end -- if id == nil then item connected to current page is used function Config:getLabel(id, raw, link, short) local label = nil local prefix, title= "", nil if not id then id = mw.wikibase.getEntityIdForCurrentPage() if not id then return "" end end id = id:upper() -- just to be sure if raw then -- check if given id actually exists if mw.wikibase.isValidEntityId(id) and mw.wikibase.entityExists(id) then label = id end prefix, title = "d:Special:EntityPage/", label -- may be nil else -- try short name first if requested if short then label = p._property{aliasesP.shortName, [p.args.eid] = id} -- get short name if label == "" then label = nil end end -- get label if not label then label = mw.wikibase.getLabel(id) end end if not label then label = "" elseif link then -- build a link if requested if not title then if id:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(id) elseif id:sub(1,1) == "P" then -- properties have no sitelink, link to Wikidata instead prefix, title = "d:Special:EntityPage/", id end end label = mw.text.nowiki(label) -- escape raw label text so it cannot be wikitext markup if title then label = buildWikilink(prefix .. title, label) end end return label end function Config:getEditIcon() local value = "" local prefix = "" local front = "&nbsp;" local back = "" if self.entityID:sub(1,1) == "P" then prefix = "Property:" end if self.editAtEnd then front = '<span style="float:' if self.langObj:isRTL() then front = front .. 'left' else front = front .. 'right' end front = front .. '">' back = '</span>' end value = "[[File:OOjs UI icon edit-ltr-progressive.svg|frameless|text-top|10px|alt=" .. i18n['info']['edit-on-wikidata'] .. "|link=https://www.wikidata.org/wiki/" .. prefix .. self.entityID .. "?uselang=" .. self.langCode if self.propertyID then value = value .. "#" .. self.propertyID elseif self.inSitelinks then value = value .. "#sitelinks-wikipedia" end value = value .. "|" .. i18n['info']['edit-on-wikidata'] .. "]]" return front .. value .. back end -- used to create the final output string when it's all done, so that for references the -- function extensionTag("ref", ...) is only called when they really ended up in the final output function Config:concatValues(valuesArray) local outString = "" local j, skip for i = 1, #valuesArray do -- check if this is a reference if valuesArray[i].refHash then j = i - 1 skip = false -- skip this reference if it is part of a continuous row of references that already contains the exact same reference while valuesArray[j] and valuesArray[j].refHash do if valuesArray[i].refHash == valuesArray[j].refHash then skip = true break end j = j - 1 end if not skip then -- add <ref> tag with the reference's hash as its name (to deduplicate references) outString = outString .. mw.getCurrentFrame():extensionTag("ref", valuesArray[i][1], {name = valuesArray[i].refHash}) end else outString = outString .. valuesArray[i][1] end end return outString end function Config:convertUnit(unit, raw, link, short, unitOnly) local space = " " local label = "" local itemID if unit == "" or unit == "1" then return nil end if unitOnly then space = "" end itemID = parseWikidataURL(unit) if itemID then if itemID == aliasesQ.percentage then return "%" else label = self:getLabel(itemID, raw, link, short) if label ~= "" then return space .. label end end end return "" end function State:getValue(snak) return self.conf:getValue(snak, self.rawValue, self.linked, self.shortName, self.anyLanguage, self.unitOnly, false, self.type:sub(1,2)) end function Config:getValue(snak, raw, link, short, anyLang, unitOnly, noSpecial, type) if snak.snaktype == 'value' then local datatype = snak.datavalue.type local subtype = snak.datatype local datavalue = snak.datavalue.value if datatype == 'string' then if subtype == 'url' and link then -- create link explicitly if raw then -- will render as a linked number like [1] return "[" .. datavalue .. "]" else return "[" .. datavalue .. " " .. datavalue .. "]" end elseif subtype == 'commonsMedia' then if link then return buildWikilink("c:File:" .. datavalue, datavalue) elseif not raw then return "[[File:" .. datavalue .. "]]" else return datavalue end elseif subtype == 'geo-shape' and link then return buildWikilink("c:" .. datavalue, datavalue) elseif subtype == 'math' and not raw then local attribute = nil if (type == parameters.property or (type == parameters.qualifier and self.propertyID == aliasesP.hasPart)) and snak.property == aliasesP.definingFormula then attribute = {qid = self.entityID} end return mw.getCurrentFrame():extensionTag("math", datavalue, attribute) elseif subtype == 'external-id' and link then local url = p._property{aliasesP.formatterURL, [p.args.eid] = snak.property} -- get formatter URL if url ~= "" then url = mw.ustring.gsub(url, "$1", datavalue) return "[" .. url .. " " .. datavalue .. "]" else return datavalue end else return datavalue end elseif datatype == 'monolingualtext' then if anyLang or datavalue['language'] == self.langCode then return datavalue['text'] else return nil end elseif datatype == 'quantity' then local value = "" local unit if not unitOnly then -- get value and strip + signs from front value = mw.ustring.gsub(datavalue['amount'], "^%+(.+)$", "%1") if raw then return value end -- replace decimal mark based on locale value = replaceDecimalMark(value) -- add delimiters for readability value = i18n.addDelimiters(value) end unit = self:convertUnit(datavalue['unit'], raw, link, short, unitOnly) if unit then value = value .. unit end return value elseif datatype == 'time' then local y, m, d, p, yDiv, yRound, yFull, value, calendarID, dateStr local yFactor = 1 local sign = 1 local prefix = "" local suffix = "" local mayAddCalendar = false local calendar = "" local precision = datavalue['precision'] if precision == 11 then p = "d" elseif precision == 10 then p = "m" else p = "y" yFactor = 10^(9-precision) end y, m, d = parseDate(datavalue['time'], p) if y < 0 then sign = -1 y = y * sign end -- if precision is tens/hundreds/thousands/millions/billions of years if precision <= 8 then yDiv = y / yFactor -- if precision is tens/hundreds/thousands of years if precision >= 6 then mayAddCalendar = true if precision <= 7 then -- round centuries/millenniums up (e.g. 20th century or 3rd millennium) yRound = math.ceil(yDiv) if not raw then if precision == 6 then suffix = i18n['datetime']['suffixes']['millennium'] else suffix = i18n['datetime']['suffixes']['century'] end suffix = i18n.getOrdinalSuffix(yRound) .. suffix else -- if not verbose, take the first year of the century/millennium -- (e.g. 1901 for 20th century or 2001 for 3rd millennium) yRound = (yRound - 1) * yFactor + 1 end else -- precision == 8 -- round decades down (e.g. 2010s) yRound = math.floor(yDiv) * yFactor if not raw then prefix = i18n['datetime']['prefixes']['decade-period'] suffix = i18n['datetime']['suffixes']['decade-period'] end end if raw and sign < 0 then -- if BCE then compensate for "counting backwards" -- (e.g. -2019 for 2010s BCE, -2000 for 20th century BCE or -3000 for 3rd millennium BCE) yRound = yRound + yFactor - 1 end else local yReFactor, yReDiv, yReRound -- round to nearest for tens of thousands of years or more yRound = math.floor(yDiv + 0.5) if yRound == 0 then if precision <= 2 and y ~= 0 then yReFactor = 1e6 yReDiv = y / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years only if we have a whole number of them precision = 3 yFactor = yReFactor yRound = yReRound end end if yRound == 0 then -- otherwise, take the unrounded (original) number of years precision = 5 yFactor = 1 yRound = y mayAddCalendar = true end end if precision >= 1 and y ~= 0 then yFull = yRound * yFactor yReFactor = 1e9 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to billions of years if we're in that range precision = 0 yFactor = yReFactor yRound = yReRound else yReFactor = 1e6 yReDiv = yFull / yReFactor yReRound = math.floor(yReDiv + 0.5) if yReDiv == yReRound then -- change precision to millions of years if we're in that range precision = 3 yFactor = yReFactor yRound = yReRound end end end if not raw then if precision == 3 then suffix = i18n['datetime']['suffixes']['million-years'] elseif precision == 0 then suffix = i18n['datetime']['suffixes']['billion-years'] else yRound = yRound * yFactor if yRound == 1 then suffix = i18n['datetime']['suffixes']['year'] else suffix = i18n['datetime']['suffixes']['years'] end end else yRound = yRound * yFactor end end else yRound = y mayAddCalendar = true end if mayAddCalendar then calendarID = parseWikidataURL(datavalue['calendarmodel']) if calendarID and calendarID == aliasesQ.prolepticJulianCalendar then if not raw then if link then calendar = " ("..buildWikilink(i18n['datetime']['julian-calendar'], i18n['datetime']['julian'])..")" else calendar = " ("..i18n['datetime']['julian']..")" end else calendar = "/"..i18n['datetime']['julian'] end end end if not raw then local ce = nil if sign < 0 then ce = i18n['datetime']['BCE'] elseif precision <= 5 then ce = i18n['datetime']['CE'] end if ce then if link then ce = buildWikilink(i18n['datetime']['common-era'], ce) end suffix = suffix .. " " .. ce end value = tostring(yRound) if m then dateStr = self.langObj:formatDate("F", "1-"..m.."-1") if d then if self.mdyDate then dateStr = dateStr .. " " .. d .. "," else dateStr = d .. " " .. dateStr end end value = dateStr .. " " .. value end value = prefix .. value .. suffix .. calendar else value = padZeros(yRound * sign, 4) if m then value = value .. "-" .. padZeros(m, 2) if d then value = value .. "-" .. padZeros(d, 2) end end value = value .. calendar end return value elseif datatype == 'globecoordinate' then -- logic from https://github.com/DataValues/Geo (v4.0.1) local precision, unitsPerDegree, numDigits, strFormat, value, globe local latitude, latConv, latValue, latLink local longitude, lonConv, lonValue, lonLink local latDirection, latDirectionN, latDirectionS, latDirectionEN local lonDirection, lonDirectionE, lonDirectionW, lonDirectionEN local degSymbol, minSymbol, secSymbol, separator local latDegrees = nil local latMinutes = nil local latSeconds = nil local lonDegrees = nil local lonMinutes = nil local lonSeconds = nil local latDegSym = "" local latMinSym = "" local latSecSym = "" local lonDegSym = "" local lonMinSym = "" local lonSecSym = "" local latDirectionEN_N = "N" local latDirectionEN_S = "S" local lonDirectionEN_E = "E" local lonDirectionEN_W = "W" if not raw then latDirectionN = i18n['coord']['latitude-north'] latDirectionS = i18n['coord']['latitude-south'] lonDirectionE = i18n['coord']['longitude-east'] lonDirectionW = i18n['coord']['longitude-west'] degSymbol = i18n['coord']['degrees'] minSymbol = i18n['coord']['minutes'] secSymbol = i18n['coord']['seconds'] separator = i18n['coord']['separator'] else latDirectionN = latDirectionEN_N latDirectionS = latDirectionEN_S lonDirectionE = lonDirectionEN_E lonDirectionW = lonDirectionEN_W degSymbol = "/" minSymbol = "/" secSymbol = "/" separator = "/" end latitude = datavalue['latitude'] longitude = datavalue['longitude'] if latitude < 0 then latDirection = latDirectionS latDirectionEN = latDirectionEN_S latitude = math.abs(latitude) else latDirection = latDirectionN latDirectionEN = latDirectionEN_N end if longitude < 0 then lonDirection = lonDirectionW lonDirectionEN = lonDirectionEN_W longitude = math.abs(longitude) else lonDirection = lonDirectionE lonDirectionEN = lonDirectionEN_E end precision = datavalue['precision'] if not precision or precision <= 0 then precision = 1 / 3600 -- precision not set (correctly), set to arcsecond end -- remove insignificant detail latitude = math.floor(latitude / precision + 0.5) * precision longitude = math.floor(longitude / precision + 0.5) * precision if precision >= 1 - (1 / 60) and precision < 1 then precision = 1 elseif precision >= (1 / 60) - (1 / 3600) and precision < (1 / 60) then precision = 1 / 60 end if precision >= 1 then unitsPerDegree = 1 elseif precision >= (1 / 60) then unitsPerDegree = 60 else unitsPerDegree = 3600 end numDigits = math.ceil(-math.log10(unitsPerDegree * precision)) if numDigits <= 0 then numDigits = tonumber("0") -- for some reason, 'numDigits = 0' may actually store '-0', so parse from string instead end strFormat = "%." .. numDigits .. "f" if precision >= 1 then latDegrees = strFormat:format(latitude) lonDegrees = strFormat:format(longitude) if not raw then latDegSym = replaceDecimalMark(latDegrees) .. degSymbol lonDegSym = replaceDecimalMark(lonDegrees) .. degSymbol else latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol end else latConv = math.floor(latitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits lonConv = math.floor(longitude * unitsPerDegree * 10^numDigits + 0.5) / 10^numDigits if precision >= (1 / 60) then latMinutes = latConv lonMinutes = lonConv else latSeconds = latConv lonSeconds = lonConv latMinutes = math.floor(latSeconds / 60) lonMinutes = math.floor(lonSeconds / 60) latSeconds = strFormat:format(latSeconds - (latMinutes * 60)) lonSeconds = strFormat:format(lonSeconds - (lonMinutes * 60)) if not raw then latSecSym = replaceDecimalMark(latSeconds) .. secSymbol lonSecSym = replaceDecimalMark(lonSeconds) .. secSymbol else latSecSym = latSeconds .. secSymbol lonSecSym = lonSeconds .. secSymbol end end latDegrees = math.floor(latMinutes / 60) lonDegrees = math.floor(lonMinutes / 60) latDegSym = latDegrees .. degSymbol lonDegSym = lonDegrees .. degSymbol latMinutes = latMinutes - (latDegrees * 60) lonMinutes = lonMinutes - (lonDegrees * 60) if precision >= (1 / 60) then latMinutes = strFormat:format(latMinutes) lonMinutes = strFormat:format(lonMinutes) if not raw then latMinSym = replaceDecimalMark(latMinutes) .. minSymbol lonMinSym = replaceDecimalMark(lonMinutes) .. minSymbol else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end else latMinSym = latMinutes .. minSymbol lonMinSym = lonMinutes .. minSymbol end end latValue = latDegSym .. latMinSym .. latSecSym .. latDirection lonValue = lonDegSym .. lonMinSym .. lonSecSym .. lonDirection value = latValue .. separator .. lonValue if link then globe = parseWikidataURL(datavalue['globe']) if globe then globe = mw.wikibase.getLabelByLang(globe, "en"):lower() else globe = "earth" end latLink = table.concat({latDegrees, latMinutes, latSeconds}, "_") lonLink = table.concat({lonDegrees, lonMinutes, lonSeconds}, "_") value = "[https://geohack.toolforge.org/geohack.php?language="..self.langCode.."&params="..latLink.."_"..latDirectionEN.."_"..lonLink.."_"..lonDirectionEN.."_globe:"..globe.." "..value.."]" end return value elseif datatype == 'wikibase-entityid' then local label local itemID = datavalue['numeric-id'] if subtype == 'wikibase-item' then itemID = "Q" .. itemID elseif subtype == 'wikibase-property' then itemID = "P" .. itemID else return '<strong class="error">' .. errorText('unknown-data-type', subtype) .. '</strong>' end label = self:getLabel(itemID, raw, link, short) if label == "" then label = nil end return label else return '<strong class="error">' .. errorText('unknown-data-type', datatype) .. '</strong>' end elseif snak.snaktype == 'somevalue' and not noSpecial then if raw then return " " -- single space represents 'somevalue' else return i18n['values']['unknown'] end elseif snak.snaktype == 'novalue' and not noSpecial then if raw then return "" -- empty string represents 'novalue' else return i18n['values']['none'] end else return nil end end function Config:getSingleRawQualifier(claim, qualifierID) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[qualifierID] end if qualifiers and qualifiers[1] then return self:getValue(qualifiers[1], true) -- raw = true else return nil end end function Config:snakEqualsValue(snak, value) local snakValue = self:getValue(snak, true) -- raw = true if snakValue and snak.snaktype == 'value' and snak.datavalue.type == 'wikibase-entityid' then value = value:upper() end return snakValue == value end function Config:setRank(rank) local rankPos if rank == p.flags.best then self.bestRank = true self.flagBest = true -- mark that 'best' flag was given return end if rank:sub(1,9) == p.flags.preferred then rankPos = 1 elseif rank:sub(1,6) == p.flags.normal then rankPos = 2 elseif rank:sub(1,10) == p.flags.deprecated then rankPos = 3 else return end -- one of the rank flags was given, check if another one was given before if not self.flagRank then self.ranks = {false, false, false} -- no other rank flag given before, so unset ranks self.bestRank = self.flagBest -- unsets bestRank only if 'best' flag was not given before self.flagRank = true -- mark that a rank flag was given end if rank:sub(-1) == "+" then for i = rankPos, 1, -1 do self.ranks[i] = true end elseif rank:sub(-1) == "-" then for i = rankPos, #self.ranks do self.ranks[i] = true end else self.ranks[rankPos] = true end end function Config:setPeriod(period) local periodPos if period == p.flags.future then periodPos = 1 elseif period == p.flags.current then periodPos = 2 elseif period == p.flags.former then periodPos = 3 else return end -- one of the period flags was given, check if another one was given before if not self.flagPeriod then self.periods = {false, false, false} -- no other period flag given before, so unset periods self.flagPeriod = true -- mark that a period flag was given end self.periods[periodPos] = true end function Config:qualifierMatches(claim, id, value) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[id] end if qualifiers then for _, v in pairs(qualifiers) do if self:snakEqualsValue(v, value) then return true end end elseif value == "" then -- if the qualifier is not present then treat it the same as the special value 'novalue' return true end return false end function Config:rankMatches(rankPos) if self.bestRank then return (self.ranks[rankPos] and self.foundRank >= rankPos) else return self.ranks[rankPos] end end function Config:timeMatches(claim) local startTime = nil local startTimeY = nil local startTimeM = nil local startTimeD = nil local endTime = nil local endTimeY = nil local endTimeM = nil local endTimeD = nil if self.periods[1] and self.periods[2] and self.periods[3] then -- any time return true end startTime = self:getSingleRawQualifier(claim, aliasesP.startTime) if startTime and startTime ~= "" and startTime ~= " " then startTimeY, startTimeM, startTimeD = parseDate(startTime) end endTime = self:getSingleRawQualifier(claim, aliasesP.endTime) if endTime and endTime ~= "" and endTime ~= " " then endTimeY, endTimeM, endTimeD = parseDate(endTime) end if startTimeY ~= nil and endTimeY ~= nil and datePrecedesDate(endTimeY, endTimeM, endTimeD, startTimeY, startTimeM, startTimeD) then -- invalidate end time if it precedes start time endTimeY = nil endTimeM = nil endTimeD = nil end if self.periods[1] then -- future if startTimeY and datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD) then return true end end if self.periods[2] then -- current if (startTimeY == nil or not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], startTimeY, startTimeM, startTimeD)) and (endTimeY == nil or datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD)) then return true end end if self.periods[3] then -- former if endTimeY and not datePrecedesDate(self.atDate[1], self.atDate[2], self.atDate[3], endTimeY, endTimeM, endTimeD) then return true end end return false end function Config:processFlag(flag) if not flag then return false end if flag == p.flags.linked then self.curState.linked = true return true elseif flag == p.flags.raw then self.curState.rawValue = true if self.curState == self.states[parameters.reference] then -- raw reference values end with periods and require a separator (other than none) self.separators["sep%r"][1] = {" "} end return true elseif flag == p.flags.short then self.curState.shortName = true return true elseif flag == p.flags.multilanguage then self.curState.anyLanguage = true return true elseif flag == p.flags.unit then self.curState.unitOnly = true return true elseif flag == p.flags.mdy then self.mdyDate = true return true elseif flag == p.flags.single then self.singleClaim = true return true elseif flag == p.flags.sourced then self.sourcedOnly = true return true elseif flag == p.flags.edit then self.editable = true return true elseif flag == p.flags.editAtEnd then self.editable = true self.editAtEnd = true return true elseif flag == p.flags.best or flag:match('^'..p.flags.preferred..'[+-]?$') or flag:match('^'..p.flags.normal..'[+-]?$') or flag:match('^'..p.flags.deprecated..'[+-]?$') then self:setRank(flag) return true elseif flag == p.flags.future or flag == p.flags.current or flag == p.flags.former then self:setPeriod(flag) return true elseif flag == "" then -- ignore empty flags and carry on return true else return false end end function Config:processFlagOrCommand(flag) local param = "" if not flag then return false end if flag == p.claimCommands.property or flag == p.claimCommands.properties then param = parameters.property elseif flag == p.claimCommands.qualifier or flag == p.claimCommands.qualifiers then self.states.qualifiersCount = self.states.qualifiersCount + 1 param = parameters.qualifier .. self.states.qualifiersCount self.separators["sep"..param] = {copyTable(defaultSeparators["sep%q\\d"])} elseif flag == p.claimCommands.reference or flag == p.claimCommands.references then param = parameters.reference else return self:processFlag(flag) end if self.states[param] then return false end -- create a new state for each command self.states[param] = State:new(self, param) -- use "%x" as the general parameter name self.states[param].parsedFormat = parseFormat(parameters.general) -- will be overwritten for param=="%p" -- set the separator self.states[param].separator = self.separators["sep"..param] -- will be nil for param=="%p", which will be set separately if flag == p.claimCommands.property or flag == p.claimCommands.qualifier or flag == p.claimCommands.reference then self.states[param].singleValue = true end self.curState = self.states[param] return true end function Config:processSeparators(args) local sep for i, v in pairs(self.separators) do if args[i] then sep = replaceSpecialChars(args[i]) if sep ~= "" then self.separators[i][1] = {sep} else self.separators[i][1] = nil end end end end function Config:setFormatAndSeparators(state, parsedFormat) state.parsedFormat = parsedFormat state.separator = self.separators["sep"] state.movSeparator = self.separators["sep"..parameters.separator] state.puncMark = self.separators["punc"] end -- determines if a claim has references by prefetching them from the claim using getReferences, -- which applies some filtering that determines if a reference is actually returned, -- and caches the references for later use function State:isSourced(claim) self.conf.prefetchedRefs = self:getReferences(claim) return (#self.conf.prefetchedRefs > 0) end function State:resetCaches() -- any prefetched references of the previous claim must not be used self.conf.prefetchedRefs = nil end function State:claimMatches(claim) local matches, rankPos -- first of all, reset any cached values used for the previous claim self:resetCaches() -- if a property value was given, check if it matches the claim's property value if self.conf.propertyValue then matches = self.conf:snakEqualsValue(claim.mainsnak, self.conf.propertyValue) else matches = true end -- if any qualifier values were given, check if each matches one of the claim's qualifier values for i, v in pairs(self.conf.qualifierIDsAndValues) do matches = (matches and self.conf:qualifierMatches(claim, i, v)) end -- check if the claim's rank and time period match rankPos = rankTable[claim.rank] or 4 matches = (matches and self.conf:rankMatches(rankPos) and self.conf:timeMatches(claim)) -- if only claims with references must be returned, check if this one has any if self.conf.sourcedOnly then matches = (matches and self:isSourced(claim)) -- prefetches and caches references end return matches, rankPos end function State:out() local result -- collection of arrays with value objects local valuesArray -- array with value objects local sep = nil -- value object local out = {} -- array with value objects local function walk(formatTable, result) local valuesArray = {} -- array with value objects for i, v in pairs(formatTable.req) do if not result[i] or not result[i][1] then -- we've got no result for a parameter that is required on this level, -- so skip this level (and its children) by returning an empty result return {} end end for _, v in ipairs(formatTable) do if v.param then valuesArray = mergeArrays(valuesArray, result[v.str]) elseif v.str ~= "" then valuesArray[#valuesArray + 1] = {v.str} end if v.child then valuesArray = mergeArrays(valuesArray, walk(v.child, result)) end end return valuesArray end -- iterate through the results from back to front, so that we know when to add separators for i = #self.results, 1, -1 do result = self.results[i] -- if there is already some output, then add the separators if #out > 0 then sep = self.separator[1] -- fixed separator result[parameters.separator] = {self.movSeparator[1]} -- movable separator else sep = nil result[parameters.separator] = {self.puncMark[1]} -- optional punctuation mark end valuesArray = walk(self.parsedFormat, result) if #valuesArray > 0 then if sep then valuesArray[#valuesArray + 1] = sep end out = mergeArrays(valuesArray, out) end end -- reset state before next iteration self.results = {} return out end -- level 1 hook function State:getProperty(claim) local value = {self:getValue(claim.mainsnak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getQualifiers(claim, param) local qualifiers if claim.qualifiers then qualifiers = claim.qualifiers[self.conf.qualifierIDs[param]] end if qualifiers then -- iterate through claim's qualifier statements to collect their values; -- return array with multiple value objects return self.conf.states[param]:iterate(qualifiers, {[parameters.general] = hookNames[parameters.qualifier.."\\d"][2], count = 1}) -- pass qualifier state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getQualifier(snak) local value = {self:getValue(snak)} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getAllQualifiers(claim, param, result, hooks) local out = {} -- array with value objects local sep = self.conf.separators["sep"..parameters.qualifier][1] -- value object -- iterate through the output of the separate "qualifier(s)" commands for i = 1, self.conf.states.qualifiersCount do -- if a hook has not been called yet, call it now if not result[parameters.qualifier..i] then self:callHook(parameters.qualifier..i, hooks, claim, result) end -- if there is output for this particular "qualifier(s)" command, then add it if result[parameters.qualifier..i] and result[parameters.qualifier..i][1] then -- if there is already some output, then add the separator if #out > 0 and sep then out[#out + 1] = sep end out = mergeArrays(out, result[parameters.qualifier..i]) end end return out end -- level 1 hook function State:getReferences(claim) if self.conf.prefetchedRefs then -- return references that have been prefetched by isSourced return self.conf.prefetchedRefs end if claim.references then -- iterate through claim's reference statements to collect their values; -- return array with multiple value objects return self.conf.states[parameters.reference]:iterate(claim.references, {[parameters.general] = hookNames[parameters.reference][2], count = 1}) -- pass reference state with level 2 hook else return {} -- return empty array end end -- level 2 hook function State:getReference(statement) local citeParamMapping = i18n['cite']['param-mapping'] local citeConfig = i18n['cite']['config'] local citeTypes = i18n['cite']['output-types'] -- will hold rendered properties of the reference which are not directly from statement.snaks, -- Namely, is URL generated from an external ID. local additionalProcessedProperties = {} -- for each citation type, there will be an associative array that associates lists of rendered properties -- to citation-template parameters local candidateParams = {} -- like above, but only associates one rendered property to each parameter; if the above variable -- contains more strings for a parameter, the strings will be assigned to numbered params (e.g. "author1") local citeParams = {} local citeErrors = {} local referenceEmpty = true -- will be set to false if at least one parameter is left unremoved local version = 12 -- increment this each time the below logic is changed to avoid conflict errors if not statement.snaks then return {} end -- don't use bot-added references referencing Wikimedia projects or containing "inferred from" (such references are not usable on Wikipedia) if statement.snaks[aliasesP.importedFrom] or statement.snaks[aliasesP.wikimediaImportURL] or statement.snaks[aliasesP.inferredFrom] then return {} end -- don't include "type of reference" if statement.snaks[aliasesP.typeOfReference] then statement.snaks[aliasesP.typeOfReference] = nil end -- don't include "image" to prevent littering if statement.snaks[aliasesP.image] then statement.snaks[aliasesP.image] = nil end -- don't include "language" if it is equal to the local one if self:getReferenceDetail(statement.snaks, aliasesP.language) == self.conf.langName then statement.snaks[aliasesP.language] = nil end if statement.snaks[aliasesP.statedIn] and not statement.snaks[aliasesP.referenceURL] then -- "stated in" was given but "reference URL" was not. -- get "Wikidata property" properties from the item in "stated in" -- if any of the returned properties of the external-id datatype is in statement.snaks, generate a link from it and use the link in the reference -- find the "Wikidata property" properties in the item from "stated in" local wikidataPropertiesOfSource = mw.text.split(p._properties{p.flags.raw, aliasesP.wikidataProperty, [p.args.eid] = self.conf:getValue(statement.snaks[aliasesP.statedIn][1], true, false)}, ", ", true) for i, wikidataPropertyOfSource in pairs(wikidataPropertiesOfSource) do if statement.snaks[wikidataPropertyOfSource] and statement.snaks[wikidataPropertyOfSource][1].datatype == "external-id" then local tempLink = self:getReferenceDetail(statement.snaks, wikidataPropertyOfSource, false, true) -- not raw, linked if mw.ustring.match(tempLink, "^%[%Z- %Z+%]$") then -- getValue returned a URL in square brackets. -- the link is in wiki markup, so strip the square brackets and the display text -- gsub also returns another, discarted value, therefore the result is assigned to tempLink first tempLink = mw.ustring.gsub(tempLink, "^%[(%Z-) %Z+%]$", "%1") additionalProcessedProperties[aliasesP.referenceURL] = {tempLink} statement.snaks[wikidataPropertyOfSource] = nil break end end end end -- initialize candidateParams and citeParams for _, citeType in ipairs(citeTypes) do candidateParams[citeType] = {} citeParams[citeType] = {} end -- fill candidateParams for _, citeType in ipairs(citeTypes) do -- This will contain value--priority pairs for each param name. local candidateValuesAndPriorities = {} -- fill candidateValuesAndPriorities for refProperty in pairs(statement.snaks) do if citeErrors[citeType] then break end repeat -- just a simple wrapper to emulate "continue" -- set mappingKey and prefix local mappingKey local prefix = "" if statement.snaks[refProperty][1].datatype == 'external-id' then mappingKey = "external-id" prefix = self.conf:getLabel(refProperty) if prefix ~= "" then prefix = prefix .. " " end else mappingKey = refProperty end local paramName = citeParamMapping[citeType][mappingKey] -- skip properties with empty parameter name if paramName == "" then break -- skip this property for this value of citeType end -- handle unknown properties in the reference if not paramName then referenceEmpty = false local error_message = errorText("unknown-property-in-ref", refProperty) assert(error_message) -- Should not be nil citeErrors[citeType] = error_message break end -- set processedProperty local processedProperty local raw = false -- if the value is wanted raw if isValueInTable(paramName, citeConfig[citeType]["raw-value-params"] or {}) then raw = true end if isValueInTable(paramName, citeConfig[citeType]["numbered-params"] or {}) then -- Multiple values may be given. processedProperty = self:getReferenceDetails(statement.snaks, refProperty, raw, self.linked, true) -- anyLang = true else -- If multiple values are given, all but the first suitable one are discarted. processedProperty = {self:getReferenceDetail(statement.snaks, refProperty, raw, self.linked and (statement.snaks[refProperty][1].datatype ~= 'url'), true)} -- link = true/false, anyLang = true end if #processedProperty == 0 then break end referenceEmpty = false -- add an empty entry to candidateValuesAndPriorities, if there isn't one already if not candidateValuesAndPriorities[paramName] then candidateValuesAndPriorities[paramName] = {} end -- find the priority of refProperty local thisPropertyPriority = -1 local thisParamPrioritization = citeConfig[citeType]["prioritization"][paramName] if thisParamPrioritization then for i_priority, i_property in ipairs(thisParamPrioritization) do if i_property == refProperty then thisPropertyPriority = i_priority end end end for _, propertyValue in pairs(processedProperty) do table.insert( candidateValuesAndPriorities[paramName], {prefix .. propertyValue, thisPropertyPriority} ) end until true end -- fill candidateParams[citeType] if not citeErrors[citeType] then local compareValuePriorities = function(pair1, pair2) if pair1[2] == -1 and pair2[2] ~= -1 then return false end if pair1[2] ~= -1 and pair2[2] == -1 then return true end return pair1[2] < pair2[2] end -- fill candidateParams[citeType][paramName] for each used param for paramName, _ in pairs(candidateValuesAndPriorities) do table.sort(candidateValuesAndPriorities[paramName], compareValuePriorities) candidateParams[citeType][paramName] = {} for _, valuePriorityPair in ipairs(candidateValuesAndPriorities[paramName]) do table.insert(candidateParams[citeType][paramName], valuePriorityPair[1]) end end end end -- handle additional properties for refProperty in pairs(additionalProcessedProperties) do for _, citeType in ipairs(citeTypes) do repeat -- skip if there already have been errors if citeErrors[citeType] then break end local paramName = citeParamMapping[citeType][refProperty] -- handle unknown properties in the reference if not paramName then -- Skip this additional property, but do not cause an error. break end if paramName == "" then break end referenceEmpty = false if not candidateParams[citeType][paramName] then candidateParams[citeType][paramName] = {} end for _, propertyValue in pairs(additionalProcessedProperties[refProperty]) do table.insert(candidateParams[citeType][paramName], propertyValue) end until true end end -- fill citeParams for _, citeType in ipairs(citeTypes) do for paramName, paramValues in pairs(candidateParams[citeType]) do if #paramValues == 1 or not isValueInTable(paramName, citeConfig[citeType]["numbered-params"] or {}) then citeParams[citeType][paramName] = paramValues[1] else -- There is more than one value for this parameter - the values will -- go into separate numbered parameters (e.g. "author1", "author2") for paramNum, paramValue in pairs(paramValues) do citeParams[citeType][paramName .. paramNum] = paramValue end end end end -- handle missing mandatory parameters for the templates for _, citeType in ipairs(citeTypes) do for _, requiredCiteParam in pairs(citeConfig[citeType]["mandatory-params"] or {}) do if not citeParams[citeType][requiredCiteParam] then -- The required param is not present. if citeErrors[citeType] then -- Do not override the previous error, if it exists. break end local error_message = errorText("missing-mandatory-param", requiredCiteParam) assert(error_message) -- Should not be nil citeErrors[citeType] = error_message end end end local citeTypeToUse = nil -- choose the output template for _, citeType in ipairs(citeTypes) do if not citeErrors[citeType] then citeTypeToUse = citeType break end end -- set refContent local refContent = "" if citeTypeToUse then local templateToUse = citeConfig[citeTypeToUse]["template"] local paramsToUse = citeParams[citeTypeToUse] if not templateToUse or templateToUse == "" then throwError("no-such-reference-template", tostring(templateToUse), i18nPath, citeTypeToUse) end -- if this module is being substituted then build a regular template call, otherwise expand the template if mw.isSubsting() then for i, v in pairs(paramsToUse) do refContent = refContent .. "|" .. i .. "=" .. v end refContent = "{{" .. templateToUse .. refContent .. "}}" else xpcall( function () refContent = mw.getCurrentFrame():expandTemplate{title=templateToUse, args=paramsToUse} end, function () throwError("no-such-reference-template", templateToUse, i18nPath, citeTypeToUse) end ) end -- If the citation couldn't be displayed using any template, but is not empty (barring ignored propeties), throw an error. elseif not referenceEmpty then refContent = errorText("malformed-reference-header") for _, citeType in ipairs(citeTypes) do refContent = refContent .. errorText("template-failure-reason", citeConfig[citeType]["template"], citeErrors[citeType]) end refContent = refContent .. errorText("malformed-reference-footer") end -- wrap refContent local ref = {} if refContent ~= "" then ref = {refContent} if not self.rawValue then -- this should become a <ref> tag, so save the reference's hash for later ref.refHash = "wikidata-" .. statement.hash .. "-v" .. (tonumber(i18n['version']) + version) end return {ref} else return {} end end -- gets a detail of one particular type for a reference function State:getReferenceDetail(snaks, dType, raw, link, anyLang) local switchLang = anyLang local value = nil if not snaks[dType] then return nil end -- if anyLang, first try the local language and otherwise any language repeat for _, v in ipairs(snaks[dType]) do value = self.conf:getValue(v, raw, link, false, anyLang and not switchLang, false, true) -- noSpecial = true if value then break end end if value or not anyLang then break end switchLang = not switchLang until anyLang and switchLang return value end -- gets the details of one particular type for a reference function State:getReferenceDetails(snaks, dType, raw, link, anyLang) local values = {} if not snaks[dType] then return {} end for _, v in ipairs(snaks[dType]) do -- if nil is returned then it will not be added to the table values[#values + 1] = self.conf:getValue(v, raw, link, false, anyLang, false, true) -- noSpecial = true end return values end -- level 1 hook function State:getAlias(object) local value = object.value local title = nil if value and self.linked then if self.conf.entityID:sub(1,1) == "Q" then title = mw.wikibase.getSitelink(self.conf.entityID) elseif self.conf.entityID:sub(1,1) == "P" then title = "d:Property:" .. self.conf.entityID end if title then value = buildWikilink(title, value) end end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end -- level 1 hook function State:getBadge(value) value = self.conf:getLabel(value, self.rawValue, self.linked, self.shortName) if value == "" then value = nil end value = {value} -- create one value object if #value > 0 then return {value} -- wrap the value object in an array and return it else return {} -- return empty array if there was no value end end function State:callHook(param, hooks, statement, result) -- call a parameter's hook if it has been defined and if it has not been called before if not result[param] and hooks[param] then local valuesArray = self[hooks[param]](self, statement, param, result, hooks) -- array with value objects -- add to the result if #valuesArray > 0 then result[param] = valuesArray result.count = result.count + 1 else result[param] = {} -- an empty array to indicate that we've tried this hook already return true -- miss == true end end return false end -- iterate through claims, claim's qualifiers or claim's references to collect values function State:iterate(statements, hooks, matchHook) matchHook = matchHook or alwaysTrue local matches = false local rankPos = nil local result, gotRequired for _, v in ipairs(statements) do -- rankPos will be nil for non-claim statements (e.g. qualifiers, references, etc.) matches, rankPos = matchHook(self, v) if matches then result = {count = 0} -- collection of arrays with value objects local function walk(formatTable) local miss for i2, v2 in pairs(formatTable.req) do -- call a hook, adding its return value to the result miss = self:callHook(i2, hooks, v, result) if miss then -- we miss a required value for this level, so return false return false end if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point breaks the loop return true end end for _, v2 in ipairs(formatTable) do if result.count == hooks.count then -- we're done if all hooks have been called; -- returning at this point prevents further childs from being processed return true end if v2.child then walk(v2.child) end end return true end gotRequired = walk(self.parsedFormat) -- only append the result if we got values for all required parameters on the root level if gotRequired then -- if we have a rankPos (only with matchHook() for complete claims), then update the foundRank if rankPos and self.conf.foundRank > rankPos then self.conf.foundRank = rankPos end -- append the result self.results[#self.results + 1] = result -- break if we only need a single value if self.singleValue then break end end end end return self:out() end local function getEntityId(arg, eid, page, allowOmitPropPrefix, globalSiteId) local id = nil local prop = nil if arg then if arg:sub(1,1) == ":" then page = arg eid = nil elseif arg:sub(1,1):upper() == "Q" or arg:sub(1,9):lower() == "property:" or allowOmitPropPrefix then eid = arg page = nil else prop = arg end end if eid then if eid:sub(1,9):lower() == "property:" then id = replaceAlias(mw.text.trim(eid:sub(10))) if id:sub(1,1):upper() ~= "P" then id = "" end else id = replaceAlias(eid) end elseif page then if page:sub(1,1) == ":" then page = mw.text.trim(page:sub(2)) end id = mw.wikibase.getEntityIdForTitle(page, globalSiteId) or "" end if not id then id = mw.wikibase.getEntityIdForCurrentPage() or "" end id = id:upper() if not mw.wikibase.isValidEntityId(id) then id = "" end return id, prop end local function nextArg(args) local arg = args[args.pointer] if arg then args.pointer = args.pointer + 1 return mw.text.trim(arg) else return nil end end local function claimCommand(args, funcName) local cfg = Config:new() cfg:processFlagOrCommand(funcName) -- process first command (== function name) local lastArg, parsedFormat, formatParams, claims, value local hooks = {count = 0} -- set the date if given; -- must come BEFORE processing the flags if args[p.args.date] then cfg.atDate = {parseDate(args[p.args.date])} cfg.periods = {false, true, false} -- change default time constraint to 'current' end -- process flags and commands repeat lastArg = nextArg(args) until not cfg:processFlagOrCommand(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID, cfg.propertyID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], false, args[p.args.globalSiteId]) if cfg.entityID == "" then return "" -- we cannot continue without a valid entity ID end cfg.entity = mw.wikibase.getEntity(cfg.entityID) if not cfg.propertyID then cfg.propertyID = nextArg(args) end cfg.propertyID = replaceAlias(cfg.propertyID) if not cfg.entity or not cfg.propertyID then return "" -- we cannot continue without an entity or a property ID end cfg.propertyID = cfg.propertyID:upper() if not cfg.entity.claims or not cfg.entity.claims[cfg.propertyID] then return "" -- there is no use to continue without any claims end claims = cfg.entity.claims[cfg.propertyID] if cfg.states.qualifiersCount > 0 then -- do further processing if "qualifier(s)" command was given if #args - args.pointer + 1 > cfg.states.qualifiersCount then -- claim ID or literal value has been given cfg.propertyValue = nextArg(args) end for i = 1, cfg.states.qualifiersCount do -- check if given qualifier ID is an alias and add it cfg.qualifierIDs[parameters.qualifier..i] = replaceAlias(nextArg(args) or ""):upper() end elseif cfg.states[parameters.reference] then -- do further processing if "reference(s)" command was given cfg.propertyValue = nextArg(args) end -- check for special property value 'somevalue' or 'novalue' if cfg.propertyValue then cfg.propertyValue = replaceSpecialChars(cfg.propertyValue) if cfg.propertyValue ~= "" and mw.text.trim(cfg.propertyValue) == "" then cfg.propertyValue = " " -- single space represents 'somevalue', whereas empty string represents 'novalue' else cfg.propertyValue = mw.text.trim(cfg.propertyValue) end end -- parse the desired format, or choose an appropriate format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) elseif cfg.states.qualifiersCount > 0 then -- "qualifier(s)" command given if cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.propertyWithQualifier) else parsedFormat, formatParams = parseFormat(formats.qualifier) end elseif cfg.states[parameters.property] then -- "propert(y|ies)" command given parsedFormat, formatParams = parseFormat(formats.property) else -- "reference(s)" command given parsedFormat, formatParams = parseFormat(formats.reference) end -- if a "qualifier(s)" command and no "propert(y|ies)" command has been given, make the movable separator a semicolon if cfg.states.qualifiersCount > 0 and not cfg.states[parameters.property] then cfg.separators["sep"..parameters.separator][1] = {";"} end -- if only "reference(s)" has been given, set the default separator to none (except when raw) if cfg.states[parameters.reference] and not cfg.states[parameters.property] and cfg.states.qualifiersCount == 0 and not cfg.states[parameters.reference].rawValue then cfg.separators["sep"][1] = nil end -- if exactly one "qualifier(s)" command has been given, make "sep%q" point to "sep%q1" to make them equivalent if cfg.states.qualifiersCount == 1 then cfg.separators["sep"..parameters.qualifier] = cfg.separators["sep"..parameters.qualifier.."1"] end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hooks that should be called (getProperty, getQualifiers, getReferences); -- only define a hook if both its command ("propert(y|ies)", "reference(s)", "qualifier(s)") and its parameter ("%p", "%r", "%q1", "%q2", "%q3") have been given for i, v in pairs(cfg.states) do -- e.g. 'formatParams["%q1"] or formatParams["%q"]' to define hook even if "%q1" was not defined to be able to build a complete value for "%q" if formatParams[i] or formatParams[i:sub(1, 2)] then hooks[i] = getHookName(i, 1) hooks.count = hooks.count + 1 end end -- the "%q" parameter is not attached to a state, but is a collection of the results of multiple states (attached to "%q1", "%q2", "%q3", ...); -- so if this parameter is given then this hook must be defined separately, but only if at least one "qualifier(s)" command has been given if formatParams[parameters.qualifier] and cfg.states.qualifiersCount > 0 then hooks[parameters.qualifier] = getHookName(parameters.qualifier, 1) hooks.count = hooks.count + 1 end -- create a state for "properties" if it doesn't exist yet, which will be used as a base configuration for each claim iteration; -- must come AFTER defining the hooks if not cfg.states[parameters.property] then cfg.states[parameters.property] = State:new(cfg, parameters.property) -- if the "single" flag has been given then this state should be equivalent to "property" (singular) if cfg.singleClaim then cfg.states[parameters.property].singleValue = true end end -- if the "sourced" flag has been given then create a state for "reference" if it doesn't exist yet, using default values, -- which must exist in order to be able to determine if a claim has any references; -- must come AFTER defining the hooks if cfg.sourcedOnly and not cfg.states[parameters.reference] then cfg:processFlagOrCommand(p.claimCommands.reference) -- use singular "reference" to minimize overhead end -- set the parsed format and the separators (and optional punctuation mark); -- must come AFTER creating the additonal states cfg:setFormatAndSeparators(cfg.states[parameters.property], parsedFormat) -- process qualifier matching values, analogous to cfg.propertyValue for i, v in pairs(args) do i = tostring(i) if i:match('^[Pp]%d+$') or aliasesP[i] then v = replaceSpecialChars(v) -- check for special qualifier value 'somevalue' if v ~= "" and mw.text.trim(v) == "" then v = " " -- single space represents 'somevalue' end cfg.qualifierIDsAndValues[replaceAlias(i):upper()] = v end end -- first sort the claims on rank to pre-define the order of output (preferred first, then normal, then deprecated) claims = sortOnRank(claims) -- then iterate through the claims to collect values value = cfg:concatValues(cfg.states[parameters.property]:iterate(claims, hooks, State.claimMatches)) -- pass property state with level 1 hooks and matchHook -- if desired, add a clickable icon that may be used to edit the returned values on Wikidata if cfg.editable and value ~= "" then value = value .. cfg:getEditIcon() end return value end local function generalCommand(args, funcName) local cfg = Config:new() cfg.curState = State:new(cfg) local lastArg local value = nil repeat lastArg = nextArg(args) until not cfg:processFlag(lastArg) -- get the entity ID from either the positional argument, the eid argument or the page argument cfg.entityID = getEntityId(lastArg, args[p.args.eid], args[p.args.page], true, args[p.args.globalSiteId]) if cfg.entityID == "" or not mw.wikibase.entityExists(cfg.entityID) then return "" -- we cannot continue without an entity end -- serve according to the given command if funcName == p.generalCommands.label then value = cfg:getLabel(cfg.entityID, cfg.curState.rawValue, cfg.curState.linked, cfg.curState.shortName) elseif funcName == p.generalCommands.title then cfg.inSitelinks = true if cfg.entityID:sub(1,1) == "Q" then value = mw.wikibase.getSitelink(cfg.entityID) end if cfg.curState.linked and value then value = buildWikilink(value) end elseif funcName == p.generalCommands.description then value = mw.wikibase.getDescription(cfg.entityID) else local parsedFormat, formatParams local hooks = {count = 0} cfg.entity = mw.wikibase.getEntity(cfg.entityID) if funcName == p.generalCommands.alias or funcName == p.generalCommands.badge then cfg.curState.singleValue = true end if funcName == p.generalCommands.alias or funcName == p.generalCommands.aliases then if not cfg.entity.aliases or not cfg.entity.aliases[cfg.langCode] then return "" -- there is no use to continue without any aliasses end local aliases = cfg.entity.aliases[cfg.langCode] -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.alias) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getAlias); -- only define the hook if the parameter ("%a") has been given if formatParams[parameters.alias] then hooks[parameters.alias] = getHookName(parameters.alias, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(aliases, hooks)) elseif funcName == p.generalCommands.badge or funcName == p.generalCommands.badges then if not cfg.entity.sitelinks or not cfg.entity.sitelinks[cfg.siteID] or not cfg.entity.sitelinks[cfg.siteID].badges then return "" -- there is no use to continue without any badges end local badges = cfg.entity.sitelinks[cfg.siteID].badges cfg.inSitelinks = true -- parse the desired format, or parse the default aliases format if args["format"] then parsedFormat, formatParams = parseFormat(args["format"]) else parsedFormat, formatParams = parseFormat(formats.badge) end -- process overridden separator values; -- must come AFTER tweaking the default separators cfg:processSeparators(args) -- define the hook that should be called (getBadge); -- only define the hook if the parameter ("%b") has been given if formatParams[parameters.badge] then hooks[parameters.badge] = getHookName(parameters.badge, 1) hooks.count = hooks.count + 1 end -- set the parsed format and the separators (and optional punctuation mark) cfg:setFormatAndSeparators(cfg.curState, parsedFormat) -- iterate to collect values value = cfg:concatValues(cfg.curState:iterate(badges, hooks)) end end value = value or "" if cfg.editable and value ~= "" then -- if desired, add a clickable icon that may be used to edit the returned value on Wikidata value = value .. cfg:getEditIcon() end return value end -- modules that include this module should call the functions with an underscore prepended, e.g.: p._property(args) local function establishCommands(commandList, commandFunc) for _, commandName in pairs(commandList) do local function wikitextWrapper(frame) local args = copyTable(frame.args) args.pointer = 1 loadI18n(aliasesP, frame) return commandFunc(args, commandName) end p[commandName] = wikitextWrapper local function luaWrapper(args) args = copyTable(args) args.pointer = 1 loadI18n(aliasesP) return commandFunc(args, commandName) end p["_" .. commandName] = luaWrapper end end establishCommands(p.claimCommands, claimCommand) establishCommands(p.generalCommands, generalCommand) -- main function that is supposed to be used by wrapper templates function p.main(frame) if not mw.wikibase then return nil end local f, args loadI18n(aliasesP, frame) -- get the parent frame to take the arguments that were passed to the wrapper template frame = frame:getParent() or frame if not frame.args[1] then throwError("no-function-specified") end f = mw.text.trim(frame.args[1]) if f == "main" then throwError("main-called-twice") end assert(p["_"..f], errorText('no-such-function', f)) -- copy arguments from immutable to mutable table args = copyTable(frame.args) -- remove the function name from the list table.remove(args, 1) return p["_"..f](args) end return p j5a6l03tjwodgrvfnv3lb4x5up93wlv Module:Wd/i18n 828 46674 803494 661827 2020-04-19T10:24:42Z en>Thayts 0 Preparing update to remove inclusion of aliasesP 803494 Scribunto text/plain -- The values and functions in this submodule should be localized per wiki. -- load submodule "aliasesP" that lives next to this submodule local aliasesP = mw.loadData((...):sub(1, (...):match('^.*()/') - 1).."/aliasesP") local p = { ["errors"] = { ["unknown-data-type"] = "Unknown or unsupported datatype '$1'.", ["missing-required-parameter"] = "No required parameters defined, needing at least one", ["extra-required-parameter"] = "Parameter '$1' must be defined as optional", ["no-function-specified"] = "You must specify a function to call", -- equal to the standard module error message ["main-called-twice"] = 'The function "main" cannot be called twice', ["no-such-function"] = 'The function "$1" does not exist' -- equal to the standard module error message }, ["info"] = { ["edit-on-wikidata"] = "Edit this on Wikidata" }, ["numeric"] = { ["decimal-mark"] = ".", ["delimiter"] = "," }, ["datetime"] = { ["prefixes"] = { ["decade-period"] = "" }, ["suffixes"] = { ["decade-period"] = "s", ["millennium"] = " millennium", ["century"] = " century", ["million-years"] = " million years", ["billion-years"] = " billion years", ["year"] = " year", ["years"] = " years" }, ["julian-calendar"] = "Julian calendar", -- linked page title ["julian"] = "Julian", ["BCE"] = "BCE", ["CE"] = "CE", ["common-era"] = "Common Era" -- linked page title }, ["coord"] = { ["latitude-north"] = "N", ["latitude-south"] = "S", ["longitude-east"] = "E", ["longitude-west"] = "W", ["degrees"] = "°", ["minutes"] = "'", ["seconds"] = '"', ["separator"] = ", " }, ["values"] = { ["unknown"] = "unknown", ["none"] = "none" }, ["cite"] = { ["version"] = "2", -- increase this each time the below parameters are changed to avoid conflict errors ["web"] = { -- <= left side: all allowed reference properties for *web page sources* per https://www.wikidata.org/wiki/Help:Sources -- => right side: corresponding parameter names in (equivalent of) [[:en:Template:Cite web]] (if non-existent, keep empty i.e. "") [aliasesP.statedIn] = "website", [aliasesP.referenceURL] = "url", [aliasesP.publicationDate] = "date", [aliasesP.retrieved] = "access-date", [aliasesP.title] = "title", [aliasesP.archiveURL] = "archive-url", [aliasesP.archiveDate] = "archive-date", [aliasesP.language] = "language", [aliasesP.author] = "author", -- existence of author1, author2, author3, etc. is assumed [aliasesP.publisher] = "publisher", [aliasesP.quote] = "quote", [aliasesP.pages] = "pages" -- extra option }, ["q"] = { -- <= left side: all allowed reference properties for *sources other than web pages* per https://www.wikidata.org/wiki/Help:Sources -- => right side: corresponding parameter names in (equivalent of) [[:en:Template:Cite Q]] (if non-existent, keep empty i.e. "") [aliasesP.statedIn] = "1", [aliasesP.pages] = "pages", [aliasesP.column] = "at", [aliasesP.chapter] = "chapter", [aliasesP.sectionVerseOrParagraph] = "section", ["external-id"] = "id", -- used for any type of database property ID [aliasesP.title] = "title", [aliasesP.publicationDate] = "date", [aliasesP.retrieved] = "access-date" } } } function p.getOrdinalSuffix(num) if tostring(num):sub(-2,-2) == '1' then return "th" -- 10th, 11th, 12th, 13th, ... 19th end num = tostring(num):sub(-1) if num == '1' then return "st" elseif num == '2' then return "nd" elseif num == '3' then return "rd" else return "th" end end function p.addDelimiters(n) local left, num, right = string.match(n, "^([^%d]*%d)(%d*)(.-)$") if left and num and right then return left .. (num:reverse():gsub("(%d%d%d)", "%1" .. p['numeric']['delimiter']):reverse()) .. right else return n end end function p.init() return p end return p sam3lpkcgamwpznq8upvjw6d7qwd1lr 803495 803494 2020-04-19T10:39:31Z en>Thayts 0 Finishing update 803495 Scribunto text/plain -- The values and functions in this submodule should be localized per wiki. local p = {} function p.init(aliasesP) p = { ["errors"] = { ["unknown-data-type"] = "Unknown or unsupported datatype '$1'.", ["missing-required-parameter"] = "No required parameters defined, needing at least one", ["extra-required-parameter"] = "Parameter '$1' must be defined as optional", ["no-function-specified"] = "You must specify a function to call", -- equal to the standard module error message ["main-called-twice"] = 'The function "main" cannot be called twice', ["no-such-function"] = 'The function "$1" does not exist' -- equal to the standard module error message }, ["info"] = { ["edit-on-wikidata"] = "Edit this on Wikidata" }, ["numeric"] = { ["decimal-mark"] = ".", ["delimiter"] = "," }, ["datetime"] = { ["prefixes"] = { ["decade-period"] = "" }, ["suffixes"] = { ["decade-period"] = "s", ["millennium"] = " millennium", ["century"] = " century", ["million-years"] = " million years", ["billion-years"] = " billion years", ["year"] = " year", ["years"] = " years" }, ["julian-calendar"] = "Julian calendar", -- linked page title ["julian"] = "Julian", ["BCE"] = "BCE", ["CE"] = "CE", ["common-era"] = "Common Era" -- linked page title }, ["coord"] = { ["latitude-north"] = "N", ["latitude-south"] = "S", ["longitude-east"] = "E", ["longitude-west"] = "W", ["degrees"] = "°", ["minutes"] = "'", ["seconds"] = '"', ["separator"] = ", " }, ["values"] = { ["unknown"] = "unknown", ["none"] = "none" }, ["cite"] = { ["version"] = "2", -- increase this each time the below parameters are changed to avoid conflict errors ["web"] = { -- <= left side: all allowed reference properties for *web page sources* per https://www.wikidata.org/wiki/Help:Sources -- => right side: corresponding parameter names in (equivalent of) [[:en:Template:Cite web]] (if non-existent, keep empty i.e. "") [aliasesP.statedIn] = "website", [aliasesP.referenceURL] = "url", [aliasesP.publicationDate] = "date", [aliasesP.retrieved] = "access-date", [aliasesP.title] = "title", [aliasesP.archiveURL] = "archive-url", [aliasesP.archiveDate] = "archive-date", [aliasesP.language] = "language", [aliasesP.author] = "author", -- existence of author1, author2, author3, etc. is assumed [aliasesP.publisher] = "publisher", [aliasesP.quote] = "quote", [aliasesP.pages] = "pages" -- extra option }, ["q"] = { -- <= left side: all allowed reference properties for *sources other than web pages* per https://www.wikidata.org/wiki/Help:Sources -- => right side: corresponding parameter names in (equivalent of) [[:en:Template:Cite Q]] (if non-existent, keep empty i.e. "") [aliasesP.statedIn] = "1", [aliasesP.pages] = "pages", [aliasesP.column] = "at", [aliasesP.chapter] = "chapter", [aliasesP.sectionVerseOrParagraph] = "section", ["external-id"] = "id", -- used for any type of database property ID [aliasesP.title] = "title", [aliasesP.publicationDate] = "date", [aliasesP.retrieved] = "access-date" } } } p.getOrdinalSuffix = function(num) if tostring(num):sub(-2,-2) == '1' then return "th" -- 10th, 11th, 12th, 13th, ... 19th end num = tostring(num):sub(-1) if num == '1' then return "st" elseif num == '2' then return "nd" elseif num == '3' then return "rd" else return "th" end end p.addDelimiters = function(n) local left, num, right = string.match(n, "^([^%d]*%d)(%d*)(.-)$") if left and num and right then return left .. (num:reverse():gsub("(%d%d%d)", "%1" .. p['numeric']['delimiter']):reverse()) .. right else return n end end return p end return p o2jeg5pzdz7z1yp2ldi8unzt6gpedo5 803496 803495 2020-04-19T11:12:50Z en>Thayts 0 803496 Scribunto text/plain -- The values and functions in this submodule should be localized per wiki. local p = {} function p.init(aliasesP) p = { ["errors"] = { ["unknown-data-type"] = "Unknown or unsupported datatype '$1'.", ["missing-required-parameter"] = "No required parameters defined, needing at least one", ["extra-required-parameter"] = "Parameter '$1' must be defined as optional", ["no-function-specified"] = "You must specify a function to call", -- equal to the standard module error message ["main-called-twice"] = 'The function "main" cannot be called twice', ["no-such-function"] = 'The function "$1" does not exist' -- equal to the standard module error message }, ["info"] = { ["edit-on-wikidata"] = "Edit this on Wikidata" }, ["numeric"] = { ["decimal-mark"] = ".", ["delimiter"] = "," }, ["datetime"] = { ["prefixes"] = { ["decade-period"] = "" }, ["suffixes"] = { ["decade-period"] = "s", ["millennium"] = " millennium", ["century"] = " century", ["million-years"] = " million years", ["billion-years"] = " billion years", ["year"] = " year", ["years"] = " years" }, ["julian-calendar"] = "Julian calendar", -- linked page title ["julian"] = "Julian", ["BCE"] = "BCE", ["CE"] = "CE", ["common-era"] = "Common Era" -- linked page title }, ["coord"] = { ["latitude-north"] = "N", ["latitude-south"] = "S", ["longitude-east"] = "E", ["longitude-west"] = "W", ["degrees"] = "°", ["minutes"] = "'", ["seconds"] = '"', ["separator"] = ", " }, ["values"] = { ["unknown"] = "unknown", ["none"] = "none" }, ["cite"] = { ["version"] = "2", -- increment this each time the below parameters are changed to avoid conflict errors ["web"] = { -- <= left side: all allowed reference properties for *web page sources* per https://www.wikidata.org/wiki/Help:Sources -- => right side: corresponding parameter names in (equivalent of) [[:en:Template:Cite web]] (if non-existent, keep empty i.e. "") [aliasesP.statedIn] = "website", [aliasesP.referenceURL] = "url", [aliasesP.publicationDate] = "date", [aliasesP.retrieved] = "access-date", [aliasesP.title] = "title", [aliasesP.archiveURL] = "archive-url", [aliasesP.archiveDate] = "archive-date", [aliasesP.language] = "language", [aliasesP.author] = "author", -- existence of author1, author2, author3, etc. is assumed [aliasesP.publisher] = "publisher", [aliasesP.quote] = "quote", [aliasesP.pages] = "pages" -- extra option }, ["q"] = { -- <= left side: all allowed reference properties for *sources other than web pages* per https://www.wikidata.org/wiki/Help:Sources -- => right side: corresponding parameter names in (equivalent of) [[:en:Template:Cite Q]] (if non-existent, keep empty i.e. "") [aliasesP.statedIn] = "1", [aliasesP.pages] = "pages", [aliasesP.column] = "at", [aliasesP.chapter] = "chapter", [aliasesP.sectionVerseOrParagraph] = "section", ["external-id"] = "id", -- used for any type of database property ID [aliasesP.title] = "title", [aliasesP.publicationDate] = "date", [aliasesP.retrieved] = "access-date" } } } p.getOrdinalSuffix = function(num) if tostring(num):sub(-2,-2) == '1' then return "th" -- 10th, 11th, 12th, 13th, ... 19th end num = tostring(num):sub(-1) if num == '1' then return "st" elseif num == '2' then return "nd" elseif num == '3' then return "rd" else return "th" end end p.addDelimiters = function(n) local left, num, right = string.match(n, "^([^%d]*%d)(%d*)(.-)$") if left and num and right then return left .. (num:reverse():gsub("(%d%d%d)", "%1" .. p['numeric']['delimiter']):reverse()) .. right else return n end end return p end return p h70ieb0uxswts38nevjbf8jjyt60l0l 803497 803496 2023-05-06T16:56:37Z en>Pppery 0 Per edit request 803497 Scribunto text/plain -- The values and functions in this submodule should be localized per wiki. local p = {} function p.init(aliasesP) p = { ["errors"] = { ["unknown-data-type"] = "Unknown or unsupported datatype '$1'.", ["missing-required-parameter"] = "No required parameters defined, needing at least one", ["extra-required-parameter"] = "Parameter '$1' must be defined as optional", ["no-function-specified"] = "You must specify a function to call", -- equal to the standard module error message ["main-called-twice"] = 'The function "main" cannot be called twice', ["no-such-function"] = 'The function "$1" does not exist', -- equal to the standard module error message ["malformed-reference"] = "Error: Unable to display the reference properly. See [[Module:wd/doc#References|the documentation]] for details." }, ["info"] = { ["edit-on-wikidata"] = "Edit this on Wikidata" }, ["numeric"] = { ["decimal-mark"] = ".", ["delimiter"] = "," }, ["datetime"] = { ["prefixes"] = { ["decade-period"] = "" }, ["suffixes"] = { ["decade-period"] = "s", ["millennium"] = " millennium", ["century"] = " century", ["million-years"] = " million years", ["billion-years"] = " billion years", ["year"] = " year", ["years"] = " years" }, ["julian-calendar"] = "Julian calendar", -- linked page title ["julian"] = "Julian", ["BCE"] = "BCE", ["CE"] = "CE", ["common-era"] = "Common Era" -- linked page title }, ["coord"] = { ["latitude-north"] = "N", ["latitude-south"] = "S", ["longitude-east"] = "E", ["longitude-west"] = "W", ["degrees"] = "°", ["minutes"] = "'", ["seconds"] = '"', ["separator"] = ", " }, ["values"] = { ["unknown"] = "unknown", ["none"] = "none" }, ["cite"] = { ["version"] = "3", -- increment this each time the below parameters are changed to avoid conflict errors ["web"] = { -- <= left side: all allowed reference properties for *web page sources* per https://www.wikidata.org/wiki/Help:Sources -- => right side: corresponding parameter names in (equivalent of) [[:en:Template:Cite web]] (if non-existent, keep empty i.e. "") [aliasesP.statedIn] = "website", [aliasesP.referenceURL] = "url", [aliasesP.publicationDate] = "date", [aliasesP.retrieved] = "access-date", [aliasesP.title] = "title", [aliasesP.archiveURL] = "archive-url", [aliasesP.archiveDate] = "archive-date", [aliasesP.language] = "language", [aliasesP.author] = "author", -- existence of author1, author2, author3, etc. is assumed [aliasesP.authorNameString] = "author", [aliasesP.publisher] = "publisher", [aliasesP.quote] = "quote", [aliasesP.pages] = "pages" -- extra option }, ["q"] = { -- <= left side: all allowed reference properties for *sources other than web pages* per https://www.wikidata.org/wiki/Help:Sources -- => right side: corresponding parameter names in (equivalent of) [[:en:Template:Cite Q]] (if non-existent, keep empty i.e. "") [aliasesP.statedIn] = "1", [aliasesP.pages] = "pages", [aliasesP.column] = "at", [aliasesP.chapter] = "chapter", [aliasesP.sectionVerseOrParagraph] = "section", ["external-id"] = "id", -- used for any type of database property ID [aliasesP.title] = "title", [aliasesP.publicationDate] = "date", [aliasesP.retrieved] = "access-date" } } } p.getOrdinalSuffix = function(num) if tostring(num):sub(-2,-2) == '1' then return "th" -- 10th, 11th, 12th, 13th, ... 19th end num = tostring(num):sub(-1) if num == '1' then return "st" elseif num == '2' then return "nd" elseif num == '3' then return "rd" else return "th" end end p.addDelimiters = function(n) local left, num, right = string.match(n, "^([^%d]*%d)(%d*)(.-)$") if left and num and right then return left .. (num:reverse():gsub("(%d%d%d)", "%1" .. p['numeric']['delimiter']):reverse()) .. right else return n end end return p end return p delp2h80l8nqa22xb63mor10yq2z7ld 803498 756518 2023-06-23T17:34:03Z en>Trappist the monk 0 categorize reference errors; 803498 Scribunto text/plain -- The values and functions in this submodule should be localized per wiki. local p = {} function p.init(aliasesP) p = { ["errors"] = { ["unknown-data-type"] = "Unknown or unsupported datatype '$1'.", ["missing-required-parameter"] = "No required parameters defined, needing at least one", ["extra-required-parameter"] = "Parameter '$1' must be defined as optional", ["no-function-specified"] = "You must specify a function to call", -- equal to the standard module error message ["main-called-twice"] = 'The function "main" cannot be called twice', ["no-such-function"] = 'The function "$1" does not exist', -- equal to the standard module error message ["malformed-reference"] = "Error: Unable to display the reference properly. See [[Module:wd/doc#References|the documentation]] for details.[[Category:Module:Wd reference errors]]" }, ["info"] = { ["edit-on-wikidata"] = "Edit this on Wikidata" }, ["numeric"] = { ["decimal-mark"] = ".", ["delimiter"] = "," }, ["datetime"] = { ["prefixes"] = { ["decade-period"] = "" }, ["suffixes"] = { ["decade-period"] = "s", ["millennium"] = " millennium", ["century"] = " century", ["million-years"] = " million years", ["billion-years"] = " billion years", ["year"] = " year", ["years"] = " years" }, ["julian-calendar"] = "Julian calendar", -- linked page title ["julian"] = "Julian", ["BCE"] = "BCE", ["CE"] = "CE", ["common-era"] = "Common Era" -- linked page title }, ["coord"] = { ["latitude-north"] = "N", ["latitude-south"] = "S", ["longitude-east"] = "E", ["longitude-west"] = "W", ["degrees"] = "°", ["minutes"] = "'", ["seconds"] = '"', ["separator"] = ", " }, ["values"] = { ["unknown"] = "unknown", ["none"] = "none" }, ["cite"] = { ["version"] = "3", -- increment this each time the below parameters are changed to avoid conflict errors ["web"] = { -- <= left side: all allowed reference properties for *web page sources* per https://www.wikidata.org/wiki/Help:Sources -- => right side: corresponding parameter names in (equivalent of) [[:en:Template:Cite web]] (if non-existent, keep empty i.e. "") [aliasesP.statedIn] = "website", [aliasesP.referenceURL] = "url", [aliasesP.publicationDate] = "date", [aliasesP.retrieved] = "access-date", [aliasesP.title] = "title", [aliasesP.archiveURL] = "archive-url", [aliasesP.archiveDate] = "archive-date", [aliasesP.language] = "language", [aliasesP.author] = "author", -- existence of author1, author2, author3, etc. is assumed [aliasesP.authorNameString] = "author", [aliasesP.publisher] = "publisher", [aliasesP.quote] = "quote", [aliasesP.pages] = "pages" -- extra option }, ["q"] = { -- <= left side: all allowed reference properties for *sources other than web pages* per https://www.wikidata.org/wiki/Help:Sources -- => right side: corresponding parameter names in (equivalent of) [[:en:Template:Cite Q]] (if non-existent, keep empty i.e. "") [aliasesP.statedIn] = "1", [aliasesP.pages] = "pages", [aliasesP.column] = "at", [aliasesP.chapter] = "chapter", [aliasesP.sectionVerseOrParagraph] = "section", ["external-id"] = "id", -- used for any type of database property ID [aliasesP.title] = "title", [aliasesP.publicationDate] = "date", [aliasesP.retrieved] = "access-date" } } } p.getOrdinalSuffix = function(num) if tostring(num):sub(-2,-2) == '1' then return "th" -- 10th, 11th, 12th, 13th, ... 19th end num = tostring(num):sub(-1) if num == '1' then return "st" elseif num == '2' then return "nd" elseif num == '3' then return "rd" else return "th" end end p.addDelimiters = function(n) local left, num, right = string.match(n, "^([^%d]*%d)(%d*)(.-)$") if left and num and right then return left .. (num:reverse():gsub("(%d%d%d)", "%1" .. p['numeric']['delimiter']):reverse()) .. right else return n end end return p end return p jsazx48spsxh1tff7q1p8owi97r6jay 803499 803498 2023-07-10T18:07:36Z en>Pppery 0 per edit request 803499 Scribunto text/plain -- The values and functions in this submodule should be localized per wiki. local p = {} function p.init(aliasesP) p = { ["errors"] = { ["unknown-data-type"] = "Unknown or unsupported datatype '$1'.", ["missing-required-parameter"] = "No required parameters defined, needing at least one", ["extra-required-parameter"] = "Parameter '$1' must be defined as optional", ["no-function-specified"] = "You must specify a function to call", -- equal to the standard module error message ["main-called-twice"] = 'The function "main" cannot be called twice', ["no-such-function"] = 'The function "$1" does not exist', -- equal to the standard module error message ["malformed-reference"] = "Error: Unable to display the reference properly. See [[Module:wd/doc#References|the documentation]] for details.[[Category:Module:Wd reference errors]]" }, ["info"] = { ["edit-on-wikidata"] = "Edit this on Wikidata" }, ["numeric"] = { ["decimal-mark"] = ".", ["delimiter"] = "," }, ["datetime"] = { ["prefixes"] = { ["decade-period"] = "" }, ["suffixes"] = { ["decade-period"] = "s", ["millennium"] = " millennium", ["century"] = " century", ["million-years"] = " million years", ["billion-years"] = " billion years", ["year"] = " year", ["years"] = " years" }, ["julian-calendar"] = "Julian calendar", -- linked page title ["julian"] = "Julian", ["BCE"] = "BCE", ["CE"] = "CE", ["common-era"] = "Common Era" -- linked page title }, ["coord"] = { ["latitude-north"] = "N", ["latitude-south"] = "S", ["longitude-east"] = "E", ["longitude-west"] = "W", ["degrees"] = "°", ["minutes"] = "'", ["seconds"] = '"', ["separator"] = ", " }, ["values"] = { ["unknown"] = "unknown", ["none"] = "none" }, ["cite"] = { ["version"] = "4", -- increment this each time the below parameters are changed to avoid conflict errors ["web"] = { -- <= left side: all allowed reference properties for *web page sources* per https://www.wikidata.org/wiki/Help:Sources -- => right side: corresponding parameter names in (equivalent of) [[:en:Template:Cite web]] (if non-existent, keep empty i.e. "") [aliasesP.statedIn] = "website", [aliasesP.referenceURL] = "url", [aliasesP.publicationDate] = "date", [aliasesP.retrieved] = "access-date", [aliasesP.title] = "title", [aliasesP.archiveURL] = "archive-url", [aliasesP.archiveDate] = "archive-date", [aliasesP.language] = "language", [aliasesP.author] = "author", -- existence of author1, author2, author3, etc. is assumed [aliasesP.authorNameString] = "author", [aliasesP.publisher] = "publisher", [aliasesP.quote] = "quote", [aliasesP.pages] = "pages", -- extra option [aliasesP.publishedIn] = "website" }, ["q"] = { -- <= left side: all allowed reference properties for *sources other than web pages* per https://www.wikidata.org/wiki/Help:Sources -- => right side: corresponding parameter names in (equivalent of) [[:en:Template:Cite Q]] (if non-existent, keep empty i.e. "") [aliasesP.statedIn] = "1", [aliasesP.pages] = "pages", [aliasesP.column] = "at", [aliasesP.chapter] = "chapter", [aliasesP.sectionVerseOrParagraph] = "section", ["external-id"] = "id", -- used for any type of database property ID [aliasesP.title] = "title", [aliasesP.publicationDate] = "date", [aliasesP.retrieved] = "access-date" } } } p.getOrdinalSuffix = function(num) if tostring(num):sub(-2,-2) == '1' then return "th" -- 10th, 11th, 12th, 13th, ... 19th end num = tostring(num):sub(-1) if num == '1' then return "st" elseif num == '2' then return "nd" elseif num == '3' then return "rd" else return "th" end end p.addDelimiters = function(n) local left, num, right = string.match(n, "^([^%d]*%d)(%d*)(.-)$") if left and num and right then return left .. (num:reverse():gsub("(%d%d%d)", "%1" .. p['numeric']['delimiter']):reverse()) .. right else return n end end return p end return p jo23v6upx7wskntuutq3dsmiwz0xp63 803500 803499 2024-02-11T15:54:21Z en>Sdkb 0 Mapping [[wikidata:Property:P958|Wikidata's "section, verse, paragraph, or clause" property]] to {{cite web}}'s "at" parameter, [[Module_talk:Wd#Error|per talk]] 803500 Scribunto text/plain -- The values and functions in this submodule should be localized per wiki. local p = {} function p.init(aliasesP) p = { ["errors"] = { ["unknown-data-type"] = "Unknown or unsupported datatype '$1'.", ["missing-required-parameter"] = "No required parameters defined, needing at least one", ["extra-required-parameter"] = "Parameter '$1' must be defined as optional", ["no-function-specified"] = "You must specify a function to call", -- equal to the standard module error message ["main-called-twice"] = 'The function "main" cannot be called twice', ["no-such-function"] = 'The function "$1" does not exist', -- equal to the standard module error message ["malformed-reference"] = "Error: Unable to display the reference properly. See [[Module:wd/doc#References|the documentation]] for details.[[Category:Module:Wd reference errors]]" }, ["info"] = { ["edit-on-wikidata"] = "Edit this on Wikidata" }, ["numeric"] = { ["decimal-mark"] = ".", ["delimiter"] = "," }, ["datetime"] = { ["prefixes"] = { ["decade-period"] = "" }, ["suffixes"] = { ["decade-period"] = "s", ["millennium"] = " millennium", ["century"] = " century", ["million-years"] = " million years", ["billion-years"] = " billion years", ["year"] = " year", ["years"] = " years" }, ["julian-calendar"] = "Julian calendar", -- linked page title ["julian"] = "Julian", ["BCE"] = "BCE", ["CE"] = "CE", ["common-era"] = "Common Era" -- linked page title }, ["coord"] = { ["latitude-north"] = "N", ["latitude-south"] = "S", ["longitude-east"] = "E", ["longitude-west"] = "W", ["degrees"] = "°", ["minutes"] = "'", ["seconds"] = '"', ["separator"] = ", " }, ["values"] = { ["unknown"] = "unknown", ["none"] = "none" }, ["cite"] = { ["version"] = "5", -- increment this each time the below parameters are changed to avoid conflict errors ["web"] = { -- <= left side: all allowed reference properties for *web page sources* per https://www.wikidata.org/wiki/Help:Sources -- => right side: corresponding parameter names in (equivalent of) [[:en:Template:Cite web]] (if non-existent, keep empty i.e. "") [aliasesP.statedIn] = "website", [aliasesP.referenceURL] = "url", [aliasesP.publicationDate] = "date", [aliasesP.retrieved] = "access-date", [aliasesP.title] = "title", [aliasesP.archiveURL] = "archive-url", [aliasesP.archiveDate] = "archive-date", [aliasesP.language] = "language", [aliasesP.author] = "author", -- existence of author1, author2, author3, etc. is assumed [aliasesP.authorNameString] = "author", [aliasesP.publisher] = "publisher", [aliasesP.quote] = "quote", [aliasesP.pages] = "pages", -- extra option [aliasesP.publishedIn] = "website", [aliasesP.sectionVerseOrParagraph] = "at" }, ["q"] = { -- <= left side: all allowed reference properties for *sources other than web pages* per https://www.wikidata.org/wiki/Help:Sources -- => right side: corresponding parameter names in (equivalent of) [[:en:Template:Cite Q]] (if non-existent, keep empty i.e. "") [aliasesP.statedIn] = "1", [aliasesP.pages] = "pages", [aliasesP.column] = "at", [aliasesP.chapter] = "chapter", [aliasesP.sectionVerseOrParagraph] = "section", ["external-id"] = "id", -- used for any type of database property ID [aliasesP.title] = "title", [aliasesP.publicationDate] = "date", [aliasesP.retrieved] = "access-date" } } } p.getOrdinalSuffix = function(num) if tostring(num):sub(-2,-2) == '1' then return "th" -- 10th, 11th, 12th, 13th, ... 19th end num = tostring(num):sub(-1) if num == '1' then return "st" elseif num == '2' then return "nd" elseif num == '3' then return "rd" else return "th" end end p.addDelimiters = function(n) local left, num, right = string.match(n, "^([^%d]*%d)(%d*)(.-)$") if left and num and right then return left .. (num:reverse():gsub("(%d%d%d)", "%1" .. p['numeric']['delimiter']):reverse()) .. right else return n end end return p end return p t28rz4790z52gadkxdwisqlpv6mjteu 803501 803500 2024-08-26T09:41:07Z en>Janhrach 0 update from sandbox -- layout and configurability -- changes are *not* backward-compatible 803501 Scribunto text/plain -- The values and functions in this submodule should be localized per wiki. local p = {} function p.init(aliasesP) p = { ["errors"] = { ["unknown-data-type"] = "Unknown or unsupported datatype '$1'.", ["missing-required-parameter"] = "No required parameters defined, needing at least one", ["extra-required-parameter"] = "Parameter '$1' must be defined as optional", ["no-function-specified"] = "You must specify a function to call", -- equal to the standard module error message ["main-called-twice"] = 'The function "main" cannot be called twice', ["no-such-function"] = 'The function "$1" does not exist', -- equal to the standard module error message ["malformed-reference"] = "Error: Unable to display the reference properly. See [[Module:wd/doc#References|the documentation]] for details.", ["no-such-reference-template"] = 'Error: template "%s", which is set in %s as the output template for the citation-output type "%s", does not exist' }, ["info"] = { ["edit-on-wikidata"] = "Edit this on Wikidata" }, ["numeric"] = { ["decimal-mark"] = ".", ["delimiter"] = "," }, ["datetime"] = { ["prefixes"] = { ["decade-period"] = "" }, ["suffixes"] = { ["decade-period"] = "s", ["millennium"] = " millennium", ["century"] = " century", ["million-years"] = " million years", ["billion-years"] = " billion years", ["year"] = " year", ["years"] = " years" }, ["julian-calendar"] = "Julian calendar", -- linked page title ["julian"] = "Julian", ["BCE"] = "BCE", ["CE"] = "CE", ["common-era"] = "Common Era" -- linked page title }, ["coord"] = { ["latitude-north"] = "N", ["latitude-south"] = "S", ["longitude-east"] = "E", ["longitude-west"] = "W", ["degrees"] = "°", ["minutes"] = "'", ["seconds"] = '"', ["separator"] = ", " }, ["values"] = { ["unknown"] = "unknown", ["none"] = "none" }, ["cite"] = { ["version"] = "6", -- increment this each time the below parameters are changed to avoid conflict errors ["output-types"] = {"web", "q"}, -- In this order, the output types will be tried ["param-mapping"] = { ["web"] = { -- <= left side: all allowed reference properties for *web page sources* per https://www.wikidata.org/wiki/Help:Sources -- => right side: corresponding parameter names in (equivalent of) [[:en:Template:Cite web]] (if non-existent, keep empty i.e. "") [aliasesP.statedIn] = "website", [aliasesP.referenceURL] = "url", [aliasesP.publicationDate] = "date", [aliasesP.retrieved] = "access-date", [aliasesP.title] = "title", [aliasesP.archiveURL] = "archive-url", [aliasesP.archiveDate] = "archive-date", [aliasesP.language] = "language", [aliasesP.author] = "author", [aliasesP.authorNameString] = "author", [aliasesP.publisher] = "publisher", [aliasesP.quote] = "quote", [aliasesP.pages] = "pages", -- extra option [aliasesP.publishedIn] = "website", [aliasesP.sectionVerseOrParagraph] = "at" }, ["q"] = { -- <= left side: all allowed reference properties for *sources other than web pages* per https://www.wikidata.org/wiki/Help:Sources -- => right side: corresponding parameter names in (equivalent of) [[:en:Template:Cite Q]] (if non-existent, keep empty i.e. "") [aliasesP.statedIn] = "1", [aliasesP.pages] = "pages", [aliasesP.column] = "at", [aliasesP.chapter] = "chapter", [aliasesP.sectionVerseOrParagraph] = "section", ["external-id"] = "id", -- used for any type of database property ID [aliasesP.title] = "title", [aliasesP.publicationDate] = "date", [aliasesP.retrieved] = "access-date" } }, ["config"] = { -- supported fields: -- - template: name of the template used for output -- - numbered-params: citation params accepting an arbitrary number of values by numbering the params (e.g. author1, author2) -- - raw-value-params: params taking a raw value (which means the property is rendered with getValue with raw=true) -- - mandatory-params: params that are required be in the template call (after potentially appending numbers to params listed in numbered-params) -- Leaving out the "template" field causes the output type to be ignored. ["web"] = { ["template"] = "Cite web", ["numbered-params"] = {"author"}, ["mandatory-params"] = {"url"} }, ["q"] = { ["template"] = "Cite Q", ["raw-value-params"] = {"1"}, -- the first, unnamed parameter of CiteQ takes a QID, not the name of the item cited ["mandatory-params"] = {"1"} } } } } p.getOrdinalSuffix = function(num) if tostring(num):sub(-2,-2) == '1' then return "th" -- 10th, 11th, 12th, 13th, ... 19th end num = tostring(num):sub(-1) if num == '1' then return "st" elseif num == '2' then return "nd" elseif num == '3' then return "rd" else return "th" end end p.addDelimiters = function(n) local left, num, right = string.match(n, "^([^%d]*%d)(%d*)(.-)$") if left and num and right then return left .. (num:reverse():gsub("(%d%d%d)", "%1" .. p['numeric']['delimiter']):reverse()) .. right else return n end end return p end return p ftulxnol0x4pzd6857feese1yizwn1x 803502 803501 2024-08-27T16:51:56Z en>Janhrach 0 I do not know how, but I accidentally removed the tracking category. Fixing. 803502 Scribunto text/plain -- The values and functions in this submodule should be localized per wiki. local p = {} function p.init(aliasesP) p = { ["errors"] = { ["unknown-data-type"] = "Unknown or unsupported datatype '$1'.", ["missing-required-parameter"] = "No required parameters defined, needing at least one", ["extra-required-parameter"] = "Parameter '$1' must be defined as optional", ["no-function-specified"] = "You must specify a function to call", -- equal to the standard module error message ["main-called-twice"] = 'The function "main" cannot be called twice', ["no-such-function"] = 'The function "$1" does not exist', -- equal to the standard module error message ["malformed-reference"] = "Error: Unable to display the reference properly. See [[Module:wd/doc#References|the documentation]] for details.[[Category:Module:Wd reference errors]]", ["no-such-reference-template"] = 'Error: template "%s", which is set in %s as the output template for the citation-output type "%s", does not exist' }, ["info"] = { ["edit-on-wikidata"] = "Edit this on Wikidata" }, ["numeric"] = { ["decimal-mark"] = ".", ["delimiter"] = "," }, ["datetime"] = { ["prefixes"] = { ["decade-period"] = "" }, ["suffixes"] = { ["decade-period"] = "s", ["millennium"] = " millennium", ["century"] = " century", ["million-years"] = " million years", ["billion-years"] = " billion years", ["year"] = " year", ["years"] = " years" }, ["julian-calendar"] = "Julian calendar", -- linked page title ["julian"] = "Julian", ["BCE"] = "BCE", ["CE"] = "CE", ["common-era"] = "Common Era" -- linked page title }, ["coord"] = { ["latitude-north"] = "N", ["latitude-south"] = "S", ["longitude-east"] = "E", ["longitude-west"] = "W", ["degrees"] = "°", ["minutes"] = "'", ["seconds"] = '"', ["separator"] = ", " }, ["values"] = { ["unknown"] = "unknown", ["none"] = "none" }, ["cite"] = { ["version"] = "6", -- increment this each time the below parameters are changed to avoid conflict errors ["output-types"] = {"web", "q"}, -- In this order, the output types will be tried ["param-mapping"] = { ["web"] = { -- <= left side: all allowed reference properties for *web page sources* per https://www.wikidata.org/wiki/Help:Sources -- => right side: corresponding parameter names in (equivalent of) [[:en:Template:Cite web]] (if non-existent, keep empty i.e. "") [aliasesP.statedIn] = "website", [aliasesP.referenceURL] = "url", [aliasesP.publicationDate] = "date", [aliasesP.retrieved] = "access-date", [aliasesP.title] = "title", [aliasesP.archiveURL] = "archive-url", [aliasesP.archiveDate] = "archive-date", [aliasesP.language] = "language", [aliasesP.author] = "author", [aliasesP.authorNameString] = "author", [aliasesP.publisher] = "publisher", [aliasesP.quote] = "quote", [aliasesP.pages] = "pages", -- extra option [aliasesP.publishedIn] = "website", [aliasesP.sectionVerseOrParagraph] = "at" }, ["q"] = { -- <= left side: all allowed reference properties for *sources other than web pages* per https://www.wikidata.org/wiki/Help:Sources -- => right side: corresponding parameter names in (equivalent of) [[:en:Template:Cite Q]] (if non-existent, keep empty i.e. "") [aliasesP.statedIn] = "1", [aliasesP.pages] = "pages", [aliasesP.column] = "at", [aliasesP.chapter] = "chapter", [aliasesP.sectionVerseOrParagraph] = "section", ["external-id"] = "id", -- used for any type of database property ID [aliasesP.title] = "title", [aliasesP.publicationDate] = "date", [aliasesP.retrieved] = "access-date" } }, ["config"] = { -- supported fields: -- - template: name of the template used for output -- - numbered-params: citation params accepting an arbitrary number of values by numbering the params (e.g. author1, author2) -- - raw-value-params: params taking a raw value (which means the property is rendered with getValue with raw=true) -- - mandatory-params: params that are required be in the template call (after potentially appending numbers to params listed in numbered-params) -- Leaving out the "template" field causes the output type to be ignored. ["web"] = { ["template"] = "Cite web", ["numbered-params"] = {"author"}, ["mandatory-params"] = {"url"} }, ["q"] = { ["template"] = "Cite Q", ["raw-value-params"] = {"1"}, -- the first, unnamed parameter of CiteQ takes a QID, not the name of the item cited ["mandatory-params"] = {"1"} } } } } p.getOrdinalSuffix = function(num) if tostring(num):sub(-2,-2) == '1' then return "th" -- 10th, 11th, 12th, 13th, ... 19th end num = tostring(num):sub(-1) if num == '1' then return "st" elseif num == '2' then return "nd" elseif num == '3' then return "rd" else return "th" end end p.addDelimiters = function(n) local left, num, right = string.match(n, "^([^%d]*%d)(%d*)(.-)$") if left and num and right then return left .. (num:reverse():gsub("(%d%d%d)", "%1" .. p['numeric']['delimiter']):reverse()) .. right else return n end end return p end return p 43r1q7uzi8u36rmnl0nou4mjq944kjk 803503 803502 2024-09-02T16:58:45Z en>Janhrach 0 companion edit to the main-module one (desc.: "citation error message improvement and related internal tweaks") 803503 Scribunto text/plain -- The values and functions in this submodule should be localized per wiki. local p = {} function p.init(aliasesP) p = { ["version"] = "7", -- increment this each time the below parameters are changed to avoid reference conflict errors ["errors"] = { ["unknown-data-type"] = "Unknown or unsupported datatype '%s'.", ["missing-required-parameter"] = "No required parameters defined, needing at least one", ["extra-required-parameter"] = "Parameter '%s' must be defined as optional", ["no-function-specified"] = "You must specify a function to call", -- equal to the standard module error message ["main-called-twice"] = 'The function "main" cannot be called twice', ["no-such-function"] = 'The function "%s" does not exist', -- equal to the standard module error message ["no-such-reference-template"] = 'Error: template "%s", which is set in %s as the output template for the citation-output type "%s", does not exist', -- Parts of the error message signalling a malformed reference. ["malformed-reference-header"] = "<span style=\"color:#dd3333\">\nError: Unable to display the reference from Wikidata properly. Technical details:\n", ["malformed-reference-footer"] = "See [[Module:wd/doc#References|the documentation]] for further details.\n</span>\n[[Category:Module:Wd reference errors]]", ["template-failure-reason"] = "* Reason for the failure of {{tl|%s}}: %s\n", ["missing-mandatory-param"] = 'The output template call would miss the mandatory parameter <code>%s</code>.', ["unknown-property-in-ref"] = 'The Wikidata reference contains the property {{property|%s}}, which is not assigned to any parameter of this template.' }, ["info"] = { ["edit-on-wikidata"] = "Edit this on Wikidata" }, ["numeric"] = { ["decimal-mark"] = ".", ["delimiter"] = "," }, ["datetime"] = { ["prefixes"] = { ["decade-period"] = "" }, ["suffixes"] = { ["decade-period"] = "s", ["millennium"] = " millennium", ["century"] = " century", ["million-years"] = " million years", ["billion-years"] = " billion years", ["year"] = " year", ["years"] = " years" }, ["julian-calendar"] = "Julian calendar", -- linked page title ["julian"] = "Julian", ["BCE"] = "BCE", ["CE"] = "CE", ["common-era"] = "Common Era" -- linked page title }, ["coord"] = { ["latitude-north"] = "N", ["latitude-south"] = "S", ["longitude-east"] = "E", ["longitude-west"] = "W", ["degrees"] = "°", ["minutes"] = "'", ["seconds"] = '"', ["separator"] = ", " }, ["values"] = { ["unknown"] = "unknown", ["none"] = "none" }, ["cite"] = { ["output-types"] = {"web", "q"}, -- In this order, the output types will be tried ["param-mapping"] = { ["web"] = { -- <= left side: all allowed reference properties for *web page sources* per https://www.wikidata.org/wiki/Help:Sources -- => right side: corresponding parameter names in (equivalent of) [[:en:Template:Cite web]] (if non-existent, keep empty i.e. "") [aliasesP.statedIn] = "website", [aliasesP.referenceURL] = "url", [aliasesP.publicationDate] = "date", [aliasesP.retrieved] = "access-date", [aliasesP.title] = "title", [aliasesP.archiveURL] = "archive-url", [aliasesP.archiveDate] = "archive-date", [aliasesP.language] = "language", [aliasesP.author] = "author", [aliasesP.authorNameString] = "author", [aliasesP.publisher] = "publisher", [aliasesP.quote] = "quote", [aliasesP.pages] = "pages", -- extra option [aliasesP.publishedIn] = "website", [aliasesP.sectionVerseOrParagraph] = "at" }, ["q"] = { -- <= left side: all allowed reference properties for *sources other than web pages* per https://www.wikidata.org/wiki/Help:Sources -- => right side: corresponding parameter names in (equivalent of) [[:en:Template:Cite Q]] (if non-existent, keep empty i.e. "") [aliasesP.statedIn] = "1", [aliasesP.pages] = "pages", [aliasesP.column] = "at", [aliasesP.chapter] = "chapter", [aliasesP.sectionVerseOrParagraph] = "section", ["external-id"] = "id", -- used for any type of database property ID [aliasesP.title] = "title", [aliasesP.publicationDate] = "date", [aliasesP.retrieved] = "access-date" } }, ["config"] = { -- supported fields: -- - template: name of the template used for output -- - numbered-params: citation params accepting an arbitrary number of values by numbering the params (e.g. author1, author2) -- - raw-value-params: params taking a raw value (which means the property is rendered with getValue with raw=true) -- - mandatory-params: params that are required be in the template call (after potentially appending numbers to params listed in numbered-params) -- Leaving out the "template" field causes the output type to be ignored. ["web"] = { ["template"] = "Cite web", ["numbered-params"] = {"author"}, ["mandatory-params"] = {"url"} }, ["q"] = { ["template"] = "Cite Q", ["raw-value-params"] = {"1"}, -- the first, unnamed parameter of CiteQ takes a QID, not the name of the item cited ["mandatory-params"] = {"1"} } } } } p.getOrdinalSuffix = function(num) if tostring(num):sub(-2,-2) == '1' then return "th" -- 10th, 11th, 12th, 13th, ... 19th end num = tostring(num):sub(-1) if num == '1' then return "st" elseif num == '2' then return "nd" elseif num == '3' then return "rd" else return "th" end end p.addDelimiters = function(n) local left, num, right = string.match(n, "^([^%d]*%d)(%d*)(.-)$") if left and num and right then return left .. (num:reverse():gsub("(%d%d%d)", "%1" .. p['numeric']['delimiter']):reverse()) .. right else return n end end return p end return p 9x26xqgg01idb8drjyqhb64or1txdtn 803504 803503 2024-10-05T13:16:59Z en>Janhrach 0 add publication-place for cite web 803504 Scribunto text/plain -- The values and functions in this submodule should be localized per wiki. local p = {} function p.init(aliasesP) p = { ["version"] = "8", -- increment this each time the below parameters are changed to avoid reference conflict errors ["errors"] = { ["unknown-data-type"] = "Unknown or unsupported datatype '%s'.", ["missing-required-parameter"] = "No required parameters defined, needing at least one", ["extra-required-parameter"] = "Parameter '%s' must be defined as optional", ["no-function-specified"] = "You must specify a function to call", -- equal to the standard module error message ["main-called-twice"] = 'The function "main" cannot be called twice', ["no-such-function"] = 'The function "%s" does not exist', -- equal to the standard module error message ["no-such-reference-template"] = 'Error: template "%s", which is set in %s as the output template for the citation-output type "%s", does not exist', -- Parts of the error message signalling a malformed reference. ["malformed-reference-header"] = "<span style=\"color:#dd3333\">\nError: Unable to display the reference from Wikidata properly. Technical details:\n", ["malformed-reference-footer"] = "See [[Module:wd/doc#References|the documentation]] for further details.\n</span>\n[[Category:Module:Wd reference errors]]", ["template-failure-reason"] = "* Reason for the failure of {{tl|%s}}: %s\n", ["missing-mandatory-param"] = 'The output template call would miss the mandatory parameter <code>%s</code>.', ["unknown-property-in-ref"] = 'The Wikidata reference contains the property {{property|%s}}, which is not assigned to any parameter of this template.' }, ["info"] = { ["edit-on-wikidata"] = "Edit this on Wikidata" }, ["numeric"] = { ["decimal-mark"] = ".", ["delimiter"] = "," }, ["datetime"] = { ["prefixes"] = { ["decade-period"] = "" }, ["suffixes"] = { ["decade-period"] = "s", ["millennium"] = " millennium", ["century"] = " century", ["million-years"] = " million years", ["billion-years"] = " billion years", ["year"] = " year", ["years"] = " years" }, ["julian-calendar"] = "Julian calendar", -- linked page title ["julian"] = "Julian", ["BCE"] = "BCE", ["CE"] = "CE", ["common-era"] = "Common Era" -- linked page title }, ["coord"] = { ["latitude-north"] = "N", ["latitude-south"] = "S", ["longitude-east"] = "E", ["longitude-west"] = "W", ["degrees"] = "°", ["minutes"] = "'", ["seconds"] = '"', ["separator"] = ", " }, ["values"] = { ["unknown"] = "unknown", ["none"] = "none" }, ["cite"] = { ["output-types"] = {"web", "q"}, -- In this order, the output types will be tried ["param-mapping"] = { ["web"] = { -- <= left side: all allowed reference properties for *web page sources* per https://www.wikidata.org/wiki/Help:Sources -- => right side: corresponding parameter names in (equivalent of) [[:en:Template:Cite web]] (if non-existent, keep empty i.e. "") [aliasesP.statedIn] = "website", [aliasesP.referenceURL] = "url", [aliasesP.publicationDate] = "date", [aliasesP.retrieved] = "access-date", [aliasesP.title] = "title", [aliasesP.archiveURL] = "archive-url", [aliasesP.archiveDate] = "archive-date", [aliasesP.language] = "language", [aliasesP.author] = "author", [aliasesP.authorNameString] = "author", [aliasesP.publisher] = "publisher", [aliasesP.quote] = "quote", [aliasesP.pages] = "pages", -- extra option [aliasesP.publishedIn] = "website", [aliasesP.sectionVerseOrParagraph] = "at", [aliasesP.placeOfPublication] = "publication-place" }, ["q"] = { -- <= left side: all allowed reference properties for *sources other than web pages* per https://www.wikidata.org/wiki/Help:Sources -- => right side: corresponding parameter names in (equivalent of) [[:en:Template:Cite Q]] (if non-existent, keep empty i.e. "") [aliasesP.statedIn] = "1", [aliasesP.pages] = "pages", [aliasesP.column] = "at", [aliasesP.chapter] = "chapter", [aliasesP.sectionVerseOrParagraph] = "section", ["external-id"] = "id", -- used for any type of database property ID [aliasesP.title] = "title", [aliasesP.publicationDate] = "date", [aliasesP.retrieved] = "access-date" } }, ["config"] = { -- supported fields: -- - template: name of the template used for output -- - numbered-params: citation params accepting an arbitrary number of values by numbering the params (e.g. author1, author2) -- - raw-value-params: params taking a raw value (which means the property is rendered with getValue with raw=true) -- - mandatory-params: params that are required be in the template call (after potentially appending numbers to params listed in numbered-params) -- Leaving out the "template" field causes the output type to be ignored. ["web"] = { ["template"] = "Cite web", ["numbered-params"] = {"author"}, ["mandatory-params"] = {"url"} }, ["q"] = { ["template"] = "Cite Q", ["raw-value-params"] = {"1"}, -- the first, unnamed parameter of CiteQ takes a QID, not the name of the item cited ["mandatory-params"] = {"1"} } } } } p.getOrdinalSuffix = function(num) if tostring(num):sub(-2,-2) == '1' then return "th" -- 10th, 11th, 12th, 13th, ... 19th end num = tostring(num):sub(-1) if num == '1' then return "st" elseif num == '2' then return "nd" elseif num == '3' then return "rd" else return "th" end end p.addDelimiters = function(n) local left, num, right = string.match(n, "^([^%d]*%d)(%d*)(.-)$") if left and num and right then return left .. (num:reverse():gsub("(%d%d%d)", "%1" .. p['numeric']['delimiter']):reverse()) .. right else return n end end return p end return p 0dgb7gzbanict0w1nisr7z00oncbn4m 803505 803504 2024-10-05T14:22:01Z en>Janhrach 0 Undid revision [[Special:Diff/1249540561|1249540561]] by [[Special:Contributions/Janhrach|Janhrach]] ([[User talk:Janhrach|talk]]) 803505 Scribunto text/plain -- The values and functions in this submodule should be localized per wiki. local p = {} function p.init(aliasesP) p = { ["version"] = "7", -- increment this each time the below parameters are changed to avoid reference conflict errors ["errors"] = { ["unknown-data-type"] = "Unknown or unsupported datatype '%s'.", ["missing-required-parameter"] = "No required parameters defined, needing at least one", ["extra-required-parameter"] = "Parameter '%s' must be defined as optional", ["no-function-specified"] = "You must specify a function to call", -- equal to the standard module error message ["main-called-twice"] = 'The function "main" cannot be called twice', ["no-such-function"] = 'The function "%s" does not exist', -- equal to the standard module error message ["no-such-reference-template"] = 'Error: template "%s", which is set in %s as the output template for the citation-output type "%s", does not exist', -- Parts of the error message signalling a malformed reference. ["malformed-reference-header"] = "<span style=\"color:#dd3333\">\nError: Unable to display the reference from Wikidata properly. Technical details:\n", ["malformed-reference-footer"] = "See [[Module:wd/doc#References|the documentation]] for further details.\n</span>\n[[Category:Module:Wd reference errors]]", ["template-failure-reason"] = "* Reason for the failure of {{tl|%s}}: %s\n", ["missing-mandatory-param"] = 'The output template call would miss the mandatory parameter <code>%s</code>.', ["unknown-property-in-ref"] = 'The Wikidata reference contains the property {{property|%s}}, which is not assigned to any parameter of this template.' }, ["info"] = { ["edit-on-wikidata"] = "Edit this on Wikidata" }, ["numeric"] = { ["decimal-mark"] = ".", ["delimiter"] = "," }, ["datetime"] = { ["prefixes"] = { ["decade-period"] = "" }, ["suffixes"] = { ["decade-period"] = "s", ["millennium"] = " millennium", ["century"] = " century", ["million-years"] = " million years", ["billion-years"] = " billion years", ["year"] = " year", ["years"] = " years" }, ["julian-calendar"] = "Julian calendar", -- linked page title ["julian"] = "Julian", ["BCE"] = "BCE", ["CE"] = "CE", ["common-era"] = "Common Era" -- linked page title }, ["coord"] = { ["latitude-north"] = "N", ["latitude-south"] = "S", ["longitude-east"] = "E", ["longitude-west"] = "W", ["degrees"] = "°", ["minutes"] = "'", ["seconds"] = '"', ["separator"] = ", " }, ["values"] = { ["unknown"] = "unknown", ["none"] = "none" }, ["cite"] = { ["output-types"] = {"web", "q"}, -- In this order, the output types will be tried ["param-mapping"] = { ["web"] = { -- <= left side: all allowed reference properties for *web page sources* per https://www.wikidata.org/wiki/Help:Sources -- => right side: corresponding parameter names in (equivalent of) [[:en:Template:Cite web]] (if non-existent, keep empty i.e. "") [aliasesP.statedIn] = "website", [aliasesP.referenceURL] = "url", [aliasesP.publicationDate] = "date", [aliasesP.retrieved] = "access-date", [aliasesP.title] = "title", [aliasesP.archiveURL] = "archive-url", [aliasesP.archiveDate] = "archive-date", [aliasesP.language] = "language", [aliasesP.author] = "author", [aliasesP.authorNameString] = "author", [aliasesP.publisher] = "publisher", [aliasesP.quote] = "quote", [aliasesP.pages] = "pages", -- extra option [aliasesP.publishedIn] = "website", [aliasesP.sectionVerseOrParagraph] = "at" }, ["q"] = { -- <= left side: all allowed reference properties for *sources other than web pages* per https://www.wikidata.org/wiki/Help:Sources -- => right side: corresponding parameter names in (equivalent of) [[:en:Template:Cite Q]] (if non-existent, keep empty i.e. "") [aliasesP.statedIn] = "1", [aliasesP.pages] = "pages", [aliasesP.column] = "at", [aliasesP.chapter] = "chapter", [aliasesP.sectionVerseOrParagraph] = "section", ["external-id"] = "id", -- used for any type of database property ID [aliasesP.title] = "title", [aliasesP.publicationDate] = "date", [aliasesP.retrieved] = "access-date" } }, ["config"] = { -- supported fields: -- - template: name of the template used for output -- - numbered-params: citation params accepting an arbitrary number of values by numbering the params (e.g. author1, author2) -- - raw-value-params: params taking a raw value (which means the property is rendered with getValue with raw=true) -- - mandatory-params: params that are required be in the template call (after potentially appending numbers to params listed in numbered-params) -- Leaving out the "template" field causes the output type to be ignored. ["web"] = { ["template"] = "Cite web", ["numbered-params"] = {"author"}, ["mandatory-params"] = {"url"} }, ["q"] = { ["template"] = "Cite Q", ["raw-value-params"] = {"1"}, -- the first, unnamed parameter of CiteQ takes a QID, not the name of the item cited ["mandatory-params"] = {"1"} } } } } p.getOrdinalSuffix = function(num) if tostring(num):sub(-2,-2) == '1' then return "th" -- 10th, 11th, 12th, 13th, ... 19th end num = tostring(num):sub(-1) if num == '1' then return "st" elseif num == '2' then return "nd" elseif num == '3' then return "rd" else return "th" end end p.addDelimiters = function(n) local left, num, right = string.match(n, "^([^%d]*%d)(%d*)(.-)$") if left and num and right then return left .. (num:reverse():gsub("(%d%d%d)", "%1" .. p['numeric']['delimiter']):reverse()) .. right else return n end end return p end return p 9x26xqgg01idb8drjyqhb64or1txdtn 803506 803505 2025-07-22T19:53:10Z en>Janhrach 0 update from sandbox 803506 Scribunto text/plain -- The values and functions in this submodule should be localized per wiki. local p = {} function p.init(aliasesP) p = { ["version"] = "8", -- increment this each time the below parameters are changed to avoid reference conflict errors ["errors"] = { ["unknown-data-type"] = "Unknown or unsupported datatype '%s'.", ["missing-required-parameter"] = "No required parameters defined, needing at least one", ["extra-required-parameter"] = "Parameter '%s' must be defined as optional", ["no-function-specified"] = "You must specify a function to call", -- equal to the standard module error message ["main-called-twice"] = 'The function "main" cannot be called twice', ["no-such-function"] = 'The function "%s" does not exist', -- equal to the standard module error message ["no-such-reference-template"] = 'Error: template "%s", which is set in %s as the output template for the citation-output type "%s", does not exist', -- Parts of the error message signalling a malformed reference. ["malformed-reference-header"] = "<span style=\"color:#dd3333\">\nError: Unable to display the reference from Wikidata properly. Technical details:\n", ["malformed-reference-footer"] = "See [[Module:wd/doc#References|the documentation]] for further details.\n</span>\n[[Category:Module:Wd reference errors]]", ["template-failure-reason"] = "* Reason for the failure of {{tl|%s}}: %s\n", ["missing-mandatory-param"] = 'The output template call would miss the mandatory parameter <code>%s</code>.', ["unknown-property-in-ref"] = 'The Wikidata reference contains the property {{property|%s}}, which is not assigned to any parameter of this template.' }, ["info"] = { ["edit-on-wikidata"] = "Edit this on Wikidata" }, ["numeric"] = { ["decimal-mark"] = ".", ["delimiter"] = "," }, ["datetime"] = { ["prefixes"] = { ["decade-period"] = "" }, ["suffixes"] = { ["decade-period"] = "s", ["millennium"] = " millennium", ["century"] = " century", ["million-years"] = " million years", ["billion-years"] = " billion years", ["year"] = " year", ["years"] = " years" }, ["julian-calendar"] = "Julian calendar", -- linked page title ["julian"] = "Julian", ["BCE"] = "BCE", ["CE"] = "CE", ["common-era"] = "Common Era" -- linked page title }, ["coord"] = { ["latitude-north"] = "N", ["latitude-south"] = "S", ["longitude-east"] = "E", ["longitude-west"] = "W", ["degrees"] = "°", ["minutes"] = "'", ["seconds"] = '"', ["separator"] = ", " }, ["values"] = { ["unknown"] = "unknown", ["none"] = "none" }, ["cite"] = { ["output-types"] = {"web", "q"}, -- In this order, the output types will be tried ["param-mapping"] = { ["web"] = { -- <= left side: all allowed reference properties for *web page sources* per https://www.wikidata.org/wiki/Help:Sources -- => right side: corresponding parameter names in (equivalent of) [[:en:Template:Cite web]] (if non-existent, keep empty i.e. "") [aliasesP.statedIn] = "website", [aliasesP.referenceURL] = "url", [aliasesP.publicationDate] = "date", [aliasesP.lastUpdate] = "date", [aliasesP.retrieved] = "access-date", [aliasesP.title] = "title", [aliasesP.subjectNamedAs] = "title", [aliasesP.archiveURL] = "archive-url", [aliasesP.archiveDate] = "archive-date", [aliasesP.language] = "language", [aliasesP.author] = "author", [aliasesP.authorNameString] = "author", [aliasesP.publisher] = "publisher", [aliasesP.quote] = "quote", [aliasesP.pages] = "pages", -- extra option [aliasesP.publishedIn] = "website", [aliasesP.sectionVerseOrParagraph] = "at" }, ["q"] = { -- <= left side: all allowed reference properties for *sources other than web pages* per https://www.wikidata.org/wiki/Help:Sources -- => right side: corresponding parameter names in (equivalent of) [[:en:Template:Cite Q]] (if non-existent, keep empty i.e. "") [aliasesP.statedIn] = "1", [aliasesP.pages] = "pages", [aliasesP.column] = "at", [aliasesP.chapter] = "chapter", [aliasesP.sectionVerseOrParagraph] = "section", ["external-id"] = "id", -- used for any type of database property ID [aliasesP.title] = "title", [aliasesP.publicationDate] = "date", [aliasesP.lastUpdate] = "date", [aliasesP.retrieved] = "access-date" } }, ["config"] = { -- supported fields: -- - template: name of the template used for output -- - numbered-params: citation params accepting an arbitrary number of values by numbering the params (e.g. author1, author2) -- - raw-value-params: params taking a raw value (which means the property is rendered with getValue with raw=true) -- - mandatory-params: params that are required be in the template call (after potentially appending numbers to params listed in numbered-params) -- - prioritization: table associating a list of properties, in the order in which they are preferred, to template parameters; -- properties not mentioned here have the lowest priority; -- prioritization of properties handled through additionalProcessedProperties is unsupported; -- no key of this table can be from numbered-params -- Leaving out the "template" field causes the output type to be ignored. ["web"] = { ["template"] = "Cite web", ["numbered-params"] = {"author"}, ["mandatory-params"] = {"url"}, ["prioritization"] = { ["date"] = {aliasesP.lastUpdate, aliasesP.publicationDate}, ["title"] = {aliasesP.title, aliasesP.subjectNamedAs} } }, ["q"] = { ["template"] = "Cite Q", ["raw-value-params"] = {"1"}, -- the first, unnamed parameter of CiteQ takes a QID, not the name of the item cited ["mandatory-params"] = {"1"}, ["prioritization"] = { ["date"] = {aliasesP.lastUpdate, aliasesP.publicationDate} } } } } } p.getOrdinalSuffix = function(num) if tostring(num):sub(-2,-2) == '1' then return "th" -- 10th, 11th, 12th, 13th, ... 19th end num = tostring(num):sub(-1) if num == '1' then return "st" elseif num == '2' then return "nd" elseif num == '3' then return "rd" else return "th" end end p.addDelimiters = function(n) local left, num, right = string.match(n, "^([^%d]*%d)(%d*)(.-)$") if left and num and right then return left .. (num:reverse():gsub("(%d%d%d)", "%1" .. p['numeric']['delimiter']):reverse()) .. right else return n end end return p end return p pgkxz3kqyoyu0zj2nmtkjkdr0ntnin5 803507 799219 2026-07-30T00:16:05Z SM7 3953 13 revisions imported from [[:en:Module:Wd/i18n]] 799219 Scribunto text/plain -- The values and functions in this submodule should be localized per wiki. local p = {} function p.init(aliasesP) p = { ["version"] = "8", -- increment this each time the below parameters are changed to avoid reference conflict errors ["errors"] = { ["unknown-data-type"] = "Unknown or unsupported datatype '%s'.", ["missing-required-parameter"] = "No required parameters defined, needing at least one", ["extra-required-parameter"] = "Parameter '%s' must be defined as optional", ["no-function-specified"] = "You must specify a function to call", -- equal to the standard module error message ["main-called-twice"] = 'The function "main" cannot be called twice', ["no-such-function"] = 'The function "%s" does not exist', -- equal to the standard module error message ["no-such-reference-template"] = 'Error: template "%s", which is set in %s as the output template for the citation-output type "%s", does not exist', -- Parts of the error message signalling a malformed reference. ["malformed-reference-header"] = "<span style=\"color:#dd3333\">\nError: Unable to display the reference from Wikidata properly. Technical details:\n", ["malformed-reference-footer"] = "See [[Module:wd/doc#References|the documentation]] for further details.\n</span>\n[[Category:Module:Wd reference errors]]", ["template-failure-reason"] = "* Reason for the failure of {{tl|%s}}: %s\n", ["missing-mandatory-param"] = 'The output template call would miss the mandatory parameter <code>%s</code>.', ["unknown-property-in-ref"] = 'The Wikidata reference contains the property {{property|%s}}, which is not assigned to any parameter of this template.' }, ["info"] = { ["edit-on-wikidata"] = "Edit this on Wikidata" }, ["numeric"] = { ["decimal-mark"] = ".", ["delimiter"] = "," }, ["datetime"] = { ["prefixes"] = { ["decade-period"] = "" }, ["suffixes"] = { ["decade-period"] = "s", ["millennium"] = " millennium", ["century"] = " century", ["million-years"] = " million years", ["billion-years"] = " billion years", ["year"] = " year", ["years"] = " years" }, ["julian-calendar"] = "Julian calendar", -- linked page title ["julian"] = "Julian", ["BCE"] = "BCE", ["CE"] = "CE", ["common-era"] = "Common Era" -- linked page title }, ["coord"] = { ["latitude-north"] = "N", ["latitude-south"] = "S", ["longitude-east"] = "E", ["longitude-west"] = "W", ["degrees"] = "°", ["minutes"] = "'", ["seconds"] = '"', ["separator"] = ", " }, ["values"] = { ["unknown"] = "unknown", ["none"] = "none" }, ["cite"] = { ["output-types"] = {"web", "q"}, -- In this order, the output types will be tried ["param-mapping"] = { ["web"] = { -- <= left side: all allowed reference properties for *web page sources* per https://www.wikidata.org/wiki/Help:Sources -- => right side: corresponding parameter names in (equivalent of) [[:en:Template:Cite web]] (if non-existent, keep empty i.e. "") [aliasesP.statedIn] = "website", [aliasesP.referenceURL] = "url", [aliasesP.publicationDate] = "date", [aliasesP.lastUpdate] = "date", [aliasesP.retrieved] = "access-date", [aliasesP.title] = "title", [aliasesP.subjectNamedAs] = "title", [aliasesP.archiveURL] = "archive-url", [aliasesP.archiveDate] = "archive-date", [aliasesP.language] = "language", [aliasesP.author] = "author", [aliasesP.authorNameString] = "author", [aliasesP.publisher] = "publisher", [aliasesP.quote] = "quote", [aliasesP.pages] = "pages", -- extra option [aliasesP.publishedIn] = "website", [aliasesP.sectionVerseOrParagraph] = "at" }, ["q"] = { -- <= left side: all allowed reference properties for *sources other than web pages* per https://www.wikidata.org/wiki/Help:Sources -- => right side: corresponding parameter names in (equivalent of) [[:en:Template:Cite Q]] (if non-existent, keep empty i.e. "") [aliasesP.statedIn] = "1", [aliasesP.pages] = "pages", [aliasesP.column] = "at", [aliasesP.chapter] = "chapter", [aliasesP.sectionVerseOrParagraph] = "section", ["external-id"] = "id", -- used for any type of database property ID [aliasesP.title] = "title", [aliasesP.publicationDate] = "date", [aliasesP.lastUpdate] = "date", [aliasesP.retrieved] = "access-date" } }, ["config"] = { -- supported fields: -- - template: name of the template used for output -- - numbered-params: citation params accepting an arbitrary number of values by numbering the params (e.g. author1, author2) -- - raw-value-params: params taking a raw value (which means the property is rendered with getValue with raw=true) -- - mandatory-params: params that are required be in the template call (after potentially appending numbers to params listed in numbered-params) -- - prioritization: table associating a list of properties, in the order in which they are preferred, to template parameters; -- properties not mentioned here have the lowest priority; -- prioritization of properties handled through additionalProcessedProperties is unsupported; -- no key of this table can be from numbered-params -- Leaving out the "template" field causes the output type to be ignored. ["web"] = { ["template"] = "Cite web", ["numbered-params"] = {"author"}, ["mandatory-params"] = {"url"}, ["prioritization"] = { ["date"] = {aliasesP.lastUpdate, aliasesP.publicationDate}, ["title"] = {aliasesP.title, aliasesP.subjectNamedAs} } }, ["q"] = { ["template"] = "Cite Q", ["raw-value-params"] = {"1"}, -- the first, unnamed parameter of CiteQ takes a QID, not the name of the item cited ["mandatory-params"] = {"1"}, ["prioritization"] = { ["date"] = {aliasesP.lastUpdate, aliasesP.publicationDate} } } } } } p.getOrdinalSuffix = function(num) if tostring(num):sub(-2,-2) == '1' then return "th" -- 10th, 11th, 12th, 13th, ... 19th end num = tostring(num):sub(-1) if num == '1' then return "st" elseif num == '2' then return "nd" elseif num == '3' then return "rd" else return "th" end end p.addDelimiters = function(n) local left, num, right = string.match(n, "^([^%d]*%d)(%d*)(.-)$") if left and num and right then return left .. (num:reverse():gsub("(%d%d%d)", "%1" .. p['numeric']['delimiter']):reverse()) .. right else return n end end return p end return p pgkxz3kqyoyu0zj2nmtkjkdr0ntnin5 एमिरेट्स (हवाई सेवा) 0 47265 803345 796018 2026-07-29T16:22:21Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803345 wikitext text/x-wiki [[File:Emirates logo.svg|thumb|230px|alt=Logo of Emitares|एमिरेट्स एयरलाइन्स के लोगो]] '''एमिरेट्स''' दुबई अन्तर्राष्ट्रीय विमानक्षेत्र, [[दुबई]] आधारित एगो वायुसेवा ह।<ref>GulfNews.com – [http://www.gulfnews.com/business/Aviation/10271373.html Emirates and Dnata now under ICD] {{Webarchive|url=https://archive.ph/20130103040416/http://www.gulfnews.com/business/Aviation/10271373.html |date=2013-01-03 }}</ref> इ मध्य पूर्व  क प्रति सप्ताह 3600 उड़ान संचालित करे वाली सबसे बड़ वायु सेवा ह। इ आपन हब से विश्व की 6 महाद्वीपन की 81 देश की 140 शहरन में सेवा देले।<ref>http://www.emirates.com/media-centre/emirates-aircraft-cover-432-million-kilometres-across-the-globe-in-six-months</ref> इ कंपनी विश्व क दस सबसे बड़  अविराम व्यापारिक उड़ान मार्ग यानि दुबई से लॉस एंजिलस, सैनफ्रांसिस्को, डैल्लास एवं हाउस्टन के भी वायु सेवा देले।<ref>{{cite web|url=https://www.cleartrip.com/flight-booking/emirates-airlines.html|title=Emirates Airlines Flight Schedules|publisher=cleartrip.com|date=|accessdate=13 June 2017|archive-date=18 January 2017|archive-url=https://web.archive.org/web/20170118190937/https://www.cleartrip.com/flight-booking/emirates-airlines.html|url-status=dead}}</ref> एमिरेट्स,  द एमिरेट्स समूह क सहभागी कंपनी ह, जेमें लगभग 50,000 कर्मचारी सेवारत बान स और इ  दुबई सरकार की पूर्ण स्वामित्व में सीधा इन्वेस्ट्मेन्ट कार्पोरेशन ऑफ़ दुबई की अधीन कार्य करेले। एकर माल यातायात (कार्गो) संबंधी सब गतिविधि एमिरेट्स समूह की एमिरेट्स स्काई कार्गो प्रभाग द्वारा संचालित होला।<ref>{{cite web|url=http://www.theemiratesgroup.com/english/our-brands/air-transportation/emirates-skycargo.aspx|title=Emirates SkyCargo|publisher=The Emirates Group|accessdate=26 August 2009}}</ref> एमिरेट्स नियमित यात्री आमद और किलोमीटर उड़ान की मामला में दुनिया क चौथा सबसे बड़ एयरलाइन ह,<ref>{{Cite web |url=http://www.iata.org/docx/WATS_2016-infographic.pdf |title=आर्काइव कॉपी के बा |access-date=2017-06-13 |archive-date=2016-07-05 |archive-url=https://web.archive.org/web/20160705112926/http://www.iata.org/docx/WATS_2016-infographic.pdf |url-status=dead }}</ref> साथे साथ अन्तर्राष्ट्रीय यात्री लोगन के सेवा प्रदान करे वाली चौथा बड़ा एयरलाइन ह।<ref>http://www.economist.com/blogs/gulliver/2015/06/worlds-largest-airlines</ref> एमिरेट्स दुबई से [[ऑकलैंड]] खातिर सबसे लंबी दुरी वाला अविराम व्यावसायिक उड़ान सेवा देवे वाला उड़ान सेवा रहे जबले की [[क़तर एयरवेज]] से इ पिछड़ नाहीं गइल। 1980 की दशक की मध्य में गल्फ एयर दुबई खातिर अपनी सेवा में कटौती करल शुरू क देले रहे। एही वजह से दुबई की शाही परिवार की सहायता से पाकिस्तान इंटरनेशनल एयरलाइन्स से भाड़ा प लिहल दुगो विमानन की साथ एमिरेट्स मार्च 1985 में आपन पहिला सेवा आरम्भ कइलस। 10 मिलियन डॉलर की आरंभिक पूंजी की साथ ए एयरलाइन्स के बिना कौनों सरकारी सहायता के आपन संचालन करे के रहे। पाकिस्तान इंटरनेशनल एयरलाइन्स अपनी एकेडमी में एमिरेट्स की केबिन क्रू के प्रशिक्षण दिहलस। इ एयरलाइन अहमद बिन सईद अल मक्तोउम, जवन की एकर वर्तमान प्रेसिडेंट हंव, उनकी नेतृत्व में शुरू कइल गइल। अपनी शुरुआत की कुछ ही वर्ष की अंदर एमिरेट्स आपन विमान दस्ता और गंतव्य की संख्या में कई गुना वृद्धि क लिहलस। अक्टूबर 2008 में एमिरेट्स आपन सब ऑपरेशन्स के दुबई इंटरनेशनल एयरपोर्ट की टर्मिनल 3 में स्थानांतरित क दिहलस।<ref>{{Cite press release|title=Emirates Announces 2009 Expansion Plan|publisher=Emirates (airline)|date=18 February 2009|url=http://www.emirates.com/english/about/news/news_detail.aspx?article=411638&offset=0|accessdate=3 August 2010}}</ref> एमिरेट्स एयरबस और बोइंग क वाइड बॉडी वाला विमानन की मिलल-जुलल दस्ता की साथ संचालन करेले और दुनिया की कुछ सिमित एयरलाइन्स में से एगो ह जवन सिर्फ वाइड बॉडी वाला विमानन से संचालन करे न स। सितंबर 2016 में एमिरेट्स एयरबस ए-380 विमानन की सबसे बड़ दस्ता कुल 83 विमान सेवा में और 59 विमान जल्द शामिल कइल जाए वाला एयरलाइन रहे। पहिला बेर शामिल कइले की साथ ही एयरबस ए-380 भीड़-भाड़ वाला लंबा दूरी की गंतव्य खातिर एमिरेट्स की पहिला पसंद रहल बा। एमिरेट्स बोइंग विमानन क परिचालन करे वाली सबसे बड़ एयरलाइन ह। ==कॉर्पोरेट प्रबंधन== [[File:Emirates employees at the inaugural flight to Brussels.jpg|thumb|right|Emirates cabin crew attendants]] इ एयरलाइन एमिरेट्स ग्रुप क एगो सहायक कंपनी ह जवन स्वयं दुबई सरकार की स्वामित्व वाली निवेश कंपनी इन्वेस्टमेंट कारपोरेशन ऑफ़ दुबई क सहायक कंपनी ह।<ref>{{cite web|author=UAEinteract.com|url=http://www.uaeinteract.com/docs/Emirates_and_Dnata_now_under_ICD/33512.htm|title=Emirates and Dnata now under ICD UAE|publisher=Uaeinteract.com|date=31 दिसंबर 2008|accessdate=15 July 2009|archive-date=25 May 2009|archive-url=https://web.archive.org/web/20090525172737/http://uaeinteract.com/docs/Emirates_and_Dnata_now_under_ICD/33512.htm|url-status=dead}}</ref><ref>{{cite web|url=http://www.gulfnews.com/business/Aviation/10271373.html|title=Emirates and Dnata now under ICD|publisher=Gulfnews|accessdate=15 July 2009|archive-date=3 January 2013|archive-url=https://archive.ph/20130103040416/http://www.gulfnews.com/business/Aviation/10271373.html|url-status=dead}}</ref><ref>{{cite web|url=http://www.arabianbusiness.com/542365-dubai-moves-ownership-of-emirates-dnata-to-icd|title=Dubai moves ownership of Emirates, Dnata to ICD – Transportation|publisher=ArabianBusiness.com|date=31 दिसंबर 2008|accessdate=15 July 2009}}</ref> आपन शुरुआत की बाद से ही, दूसरा साल के छोड़ के, एमिरेट्स कबो घाटा में नाहीं रहल और एकर सालाना वृद्धि कबो 20% से कम नाहीं रहल। आपन पहिला 11 साल में इ हर साढ़े तीन साल में और ओकरी बाद से हर चार साल में दुगना आकार वाला हो जा।<ref name="CNN">[http://money.cnn.com/magazines/business2/business2_archive/2005/10/01/8359251/index.htm Rise of the Emirates Empire] {{Webarchive|url=https://web.archive.org/web/20180703221943/http://money.cnn.com/magazines/business2/business2_archive/2005/10/01/8359251/index.htm |date=2018-07-03 }} CNN Money, 1 October 2005</ref> ==स्थान== ===संधि=== एमिरेट्स विश्व की तीनगो बड़का एयरलाइन अलायंस – वन वर्ल्ड, स्काई टीम अथवा स्टार अलायन्स - में से कौनों क सदस्य नाहीं ह। साल 2000 में थोड़ा समय खातिर इ स्टार अलायन्स में शामिल होखले प विचार कइलस पर अंत में इ आपन स्वतंत्रता कायम रखलस।<ref>{{Cite news|last=Reece|first=Damian|title=Emirates poised to join Star Alliance|publisher=Telegrapg.co.uk|date=13 August 2000|url=http://www.telegraph.co.uk/finance/4461750/Emirates-poised-to-join-Star-Alliance.html|page=11|accessdate=16 October 2010|location=London}}</ref> ==संदर्भ== <references/> ==External links== * {{Commons category inline|Emirates (airline)}} * {{Official website|https://www.emirates.com|mobile=mobile.emirates.com}} * [http://www.theemiratesgroup.com/english/ Emirates Parent Company] * [http://www.theemiratesgroup.com/english/facts-figures/annual-reports.aspx Emirates Annual Reports] * [http://www.theemiratesgroup.com/english/news-events/news-releases/news-releases.aspx Emirates News releases] * [http://www.sky-cargo.com Emirates SkyCargo] [[श्रेणी:हवाई जहाज सेवा]] 343ibyz0b105h3f7zm50vx1saj9w967 औरंगाबाद रेलवे स्टेशन 0 47268 803314 733221 2026-07-29T16:20:16Z SM7Bot 7808 /* top */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803314 wikitext text/x-wiki '''औरंगाबाद रेलवे स्टेशन''' सिकंदराबाद-मनमाड खंड पर स्थित एगो रेलवे स्टेशन ह। इ रेलवे स्टेशन दक्षिण मध्य रेलवे क्षेत्र की नांदेड़ प्रभाग की तहत आवेला। ए स्टेशन से [[हैदराबाद]], [[दिल्ली]], [[निजामाबाद]], [[नागपुर]], [[नाशिक]], [[नांदेड़]] और [[पुणे]] जईसन प्रमुख नगरन के साथ रेल संपर्क स्थापित कइल गइल बा। ==शासन प्रबंध== इ भारतीय रेलवे की दक्षिण मध्य रेलवे क्षेत्र की नांदेड डिवीजन की अंतर्गत बा। ==आर्किटेक्चर== औरंगाबाद एगो प्रसिद्ध लेकिन एगो औसत स्टेशन ह जवन महाराष्ट्र राज्य में स्थित बा। मूलतः, दक्षिण मध्य रेलवे एकर ख्याल रखेला और एके नियंत्रित करेला। इ स्टेशन एक तरफ से मुंबई से जुड़ल बा और दूसरा तरफ से हैदराबाद से जुड़ल बा। भारत की कई प्रमुख शहरन से जोड़े वाली कई गाड़ीन क सफ़र ए स्टेशन प समाप्त हो जाला, जेमें भोपाल, मुंबई, पुणे, हैदराबाद और नई दिल्ली से चले वाली गाड़ी शामिल बाड़ी स। ==कनेक्टिविटी== औरंगाबाद क [[मुंबई]], [[दिल्ली]], [[हैदराबाद]], [[निजामाबाद]], नांदेड़, [[नागपुर]], नाशिक, [[पुणे]] आदि की साथ रेल संपर्क बा। औरंगाबाद जन शताब्दी एक्सप्रेस मुंबई से एके जोड़े वाली सबसे तेज ट्रेन ह।<ref name="station info">{{cite web|title=Aurangabad railways station|url=http://indiarailinfo.com/station/map/1669|publisher=indiarailinfo|accessdate=27 June 2014}}</ref><ref>{{Cite web|url = http://www.scr.indianrailways.gov.in/view_section.jsp?lang=0&id=0,1,291|title = Divisions in SCR|date = |accessdate = 28 June 2014|website = |publisher = Indian Railways-SCR zone portal|last = |first = }}</ref><ref>{{cite web|url=https://www.cleartrip.com/trains/stations/AWB |title=Aurangabad Train Station Time Table |publisher=cleartrip.com |date= |accessdate=14 June 2017}}</ref> ==संदर्भ== <references/> ==बाहरी कड़ियाँ== * [https://web.archive.org/web/20140408180437/http://www.indianrail.gov.in/ Indian Railways website] [[श्रेणी:भारतीय रेल]] 7znr0gd8kdeetj4o8a79vo7gjk4il9a भारतीय शास्त्रीय संगीत 0 47276 803304 753078 2026-07-29T16:07:08Z SM7Bot 7808 /* इतिहास */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803304 wikitext text/x-wiki {{redirect | शास्त्रीय संगीत | यूरोप आ अमेरिका के शास्त्रीय संगीत | पच्छिमी शास्त्रीय संगीत}} '''भारतीय शास्त्रीय संगीत''' भारतीय संगीत क अभिन्न अंग ह। शास्त्रीय संगीत के ही ‘क्लासिकल म्यूजिक’ भी कहल जाला। शास्त्रीय गायन ध्वनि-प्रधान होला, शब्द-प्रधान नाहीं। एमें महत्व ध्वनि क रहेला (एकरी चढ़ाव-उतार क, शब्द और अर्थ क नाहीं)। एके जहाँ शास्त्रीय संगीत-ध्वनि विषयक साधना क अभ्यस्त कान वाला अदमी समझ सकेला, और अनभ्यस्त कान वाला भी शब्दन क अर्थ जनले मात्र से देशी गाना चाहें लोकगीतन क सुख ले सकेला। एसे बहुत अदमी स्वाभाविक तौर पर ऊब भी जाला पर एसे ऊब जईले क वजह ओ संगीतज्ञ क कमजोरी नाहीं, लोगन में जानकारी क कमी ह। ==इतिहास== भारतीय शास्त्रीय [[संगीत]] क परम्परा भरत मुनि की नाट्यशास्त्र और ओसे पहिले सामवेद की गायन ले जाला। भरत मुनि द्वारा रचित भरत नाट्य शास्त्र, भारतीय संगीत की इतिहास क प्रथम लिखित प्रमाण मानल जाला। {{Sfn|Rowell|2015|p=9–10, 59–61}}<ref name="Beck1995p107">{{harvnb|Beck|2012|pages=107–108}}, Quote: "The tradition of Indian classical music and dance known as ''Sangita'' is fundamentally rooted in the sonic and musical dimensions of the Vedas (Sama veda), Upanishads and the Agamas, such that Indian music has been nearly always religious in character".</ref> एकरी रचना की समय की बारे में कईगो मतभेद बा। आज की भारतीय शास्त्रीय संगीत की कईगो पहलु क उल्लेख ए प्राचीन ग्रंथ में मिलेला। भरत मुनि की नाटयशास्त्र की बाद मतङ्ग मुनि क बृहद्देशी और सारंगदेव रचित संगीत रत्नाकर, इतिहासी दृष्टि से सबसे महत्वपूर्ण ग्रंथ मनाला। बारहवीं सदी की पूर्वार्द्ध में लिखाईल सात अध्याय वाला ए ग्रंथ में संगीत व नृत्य क विस्तार से वर्णन बा। संगीत रत्नाकर में कईगो ताल क उल्लेख बा और ए ग्रंथ से पता चलेला कि प्राचीन भारतीय पारंपरिक संगीत में ओही समय से बदलाव आवल शुरू हो गइल रहे और संगीत पहिले से उदार होखे लागल रहे मगर मूल तत्व उहे रहल। 11वीं और 12वीं शताब्दी में मुस्लिम सभ्यता क प्रसार भइले से उत्तर भारतीय संगीत की दिशा के नया आयाम मिलल। राजदरबार संगीत क प्रमुख संरक्षक बन न स और जहां अनेक शासक लोग प्राचीन भारतीय संगीत की समृद्ध परंपरा के प्रोत्साहन दिहल लो ओहीजा अपनी आवश्यकता और रुचि अनुसार एमें अनेक बदलावो कइल लो। ओही समय कुछ नया शैली भी प्रचलन में अईली स जैसे खयाल, गज़ल आदि और भारतीय संगीत क कईगो नया वाद्य यंत्र से भी परिचय भइल जैसे सरोद, सितार वगैरह।<ref>{{cite book|ref=harv|first=Emmie|last=Te Nijenhuis |title=Indian Music: History and Structure |url=https://books.google.com/books?id=NrgfAAAAIAAJ |year=1974|publisher= BRILL Academic|isbn=90-04-03978-3 }}</ref> भारतीय संगीत क आधुनिक विद्वान् इ स्थापित क चुकल बालो कि वैदिक काल से शुरू भइल भारतीय वाद्यन क यात्रा क्रमश: एक की बाद दूसरा विशेषता जोड़ के ए यंत्रन के सँवारत गइल। एक-तंत्री वीणा ही त्रितंत्री बनल और सारिका युक्त होके मध्य-काल की पूर्व में किन्नरी वीणा की नाम से प्रसिद्ध भइल। मध्यकाल में इ यंत्र जंत्र कहलाए लागल जवन की बंगाल की कारीगरन द्वारा आज भी एही नांव से पुकारल जाला। भारत में पहुँचल मुस्लिम संगीतकार तीन तार वाली वीणा के सह (तीन) + तार = सहतार या सितार कहे लागल लो। एही प्रकार से सप्त तंत्री अथवा चित्रा-वीणा, सरोद कहलाए लागल। उत्तर भारत में मुगल राज्य ज्यादा फैलल रहे जेकरी कारण उत्तर भारतीय संगीत प मुस्लिम संस्कृति व इस्लाम क प्रभाव ज्यादा महसूस कइल गइल। जबकि दक्षिण भारत में प्रचलित संगीत कौनों प्रकार की मुस्लिम प्रभाव से अछूता रहल। बाद में सूफी आंदोलन भी भारतीय संगीत पर आपन प्रभाव जमवलस। आगे चलके देश की विभिन्न हिस्सन में कईगो नई पद्धतिन व घराना क जन्म भइल। ब्रिटिश शासनकाल की दौरान कईगो नया वाद्य प्रचलन में अईन स, पाश्चात्य संगीत से भी भारतीय संगीत क परिचय भइल। ==संदर्भ== {{reflist|33em}} [[श्रेणी:संगीत]] [[श्रेणी:भारतीय संस्कृति]] 4gqahipos85ddcgmanh8os6uwus4e62 कुमारकोम लेक रिजॉर्ट 0 47448 803346 763577 2026-07-29T16:22:25Z SM7Bot 7808 /* स्थान */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803346 wikitext text/x-wiki '''कुमारकोम लेक रिजॉर्ट''' कुमारकोम नॉर्थ पोस्ट की शांत माहौल में आइल बा।<ref>{{cite web|url=http://www.kumarakomlakeresort.in/about_kumarakom_lake_resort.html|title=Official Website|publisher=kumarakomlakeresort.in|accessdate=19 June 2017|archive-date=13 June 2017|archive-url=https://web.archive.org/web/20170613194149/http://www.kumarakomlakeresort.in/about_kumarakom_lake_resort.html|url-status=dead}}</ref><ref>{{cite web|url=http://www.vervemagazine.in/travel-and-spaces/dreamy-quietude-kumarakom-lake-resort-kerala|title=The Dreamy Quietude Of Kumarakom Lake Resort|publisher=vervemagazine.in|date=7 March 2017}}</ref> ==स्थान== इ रिसोर्ट कोट्टयम रेलवे स्टेशन से लगभग 15 किमी दूर और [[कोचीन]] अंतर्राष्ट्रीय हवाई अड्डा से 72 किमी दूर, नेडुम्बसेरी में बा। इ रिसॉर्ट एइसन जगह प बा जहाँ प्रकृति एकर चुंबन ले ले और एकरी अलावा, वेंबनाद झील, केरल बॅकवाटर और कुमारकोम पक्षी अभयारण्य जैसन स्थान एकरी नजदीक आइल बान स। कुमारकोम लेक रिज़ॉर्ट कईगो पिकनिक स्थल जैसे वाइकम बीच, पैतिरमानल द्वीप और अरुविककुशी झरना जैसन स्थान प आसान पहुँच प्रदान करेला।<ref>{{cite web|url=https://www.cleartrip.com/hotels/info/kumarakom-lake-resort-40847|title=About Kumarakom Lake Resort|publisher=cleartrip.com|accessdate=19 June 2017}}</ref> कुमारकोम लेक रिजॉर्ट चार साल ले [[भारत]] की अग्रणी रिसॉर्ट के रूप में प्रशंसित वर्ल्ड ट्रैवल अवार्ड क विजेता रही चुकल बा। एकरी आलावा एके समय समय प निम्नलिखित पुरस्कारन आ सम्मान से नवाजल गइल बा:<ref>{{cite web|url=http://prestigiousstarawards.com/qualified-venues/kumarakom-lake-resort/|title=Kumarakom Lake Resort|publisher=prestigiousstarawards.com|accessdate=19 June 2017}}</ref> * केरल 2014 की राज्य पर्यटन पुरस्कार में केरल क सर्वश्रेष्ठ पांच सितारा होटल की रूप में सम्मानित कइल गइल * भारत की प्रमुख रिसॉर्ट के रूप में विश्व यात्रा पुरस्कार 06-07, 07-08, 09-10 और 11-12 क विजेता। * एशियाई वास्तुकला उत्कृष्टता पुरस्कार खातिर नामांकित * यात्रा और अवकाश द्वारा एशिया क शीर्ष 50 रिसॉर्ट्स में से एगो। * कोंडे नेस्ट ट्रेवलर्स रीडर्स पोल में शीर्ष 100 रिसॉर्ट्स में से एगो। * वाल स्ट्रीट जर्नल द्वारा प्रशंसित 'प्लेस फॉर अ हॉलिडे पेम्पर' * भारत में शीर्ष पांचगो लक्जरी होटल में से एगो: अतुल्य भारत, सितंबर-अक्टूबर 2004। * टीटीएफ बैंगलोर में अनोखा अवकाश संपत्ति खातिर पुरस्कार जीतलस * 'बेस्ट न्यू प्रोडक्ट' की रूप में SATTE पुरस्कार विजेता 06 - 07 ==संदर्भ== [[श्रेणी:होटल]] 6emp8elylx9rczpmap2c41k6o2sea2d कोणार्क एक्सप्रेस 0 47926 803349 763583 2026-07-29T16:22:39Z SM7Bot 7808 /* कोच संरचना */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803349 wikitext text/x-wiki {{Infobox rail service |name = कोणार्क एक्सप्रेस | image= 11020 Konark Express (2).jpg | logo= Konark express 11020.jpg | caption = | type = एक्सप्रेस ट्रेन | locale = [[ओडिशा]], [[आंध्र प्रदेश]], [[तेलंगाना]], [[कर्नाटक]] आ [[महाराष्ट्र]] | first = 16 अगस्त 1978 | last = | operator = मध्य रेलवे | ridership = | start = [[भुबनेश्वर]] | stops = 35 | end = [[मुंबई]] | distance = 1932 किलोमीटर | journeytime = 37 घंटा, 15 मिनट | frequency = रोज | class = AC 2 टायर, AC 3 टायर, स्लीपर, जनरल | seating = हँऽ | sleeping = हँऽ | autorack = | catering = हँऽ, पेमेंट पर | observation = छोट ICF खिड़की | entertainment = None | baggage = सीट के नीचे | otherfacilities= | stock = | gauge = {{RailGauge|1676mm}} | el = | trainnumber = 11019 / 11020 | speed = औसतन फी घंटा 51 किलोमीटर | map = [[File:Konark express route.png|250px]] | map_state = }} '''कोनार्क एक्सप्रेस''', 1978  में मधु दंडवते द्वारा शुरू कइल गइल श्रेणीरहित कई गो सुपरफास्ट ट्रेन में से एगो रहल। ई गाड़ी [[सिकंदराबाद]] आ [[भुवनेश्वर]] की बीच में दैनिक सेवा दे। इ 2119/2120 ट्रेन की साथ चले और 2101/2102 मिनार सुपरफास्ट एक्सप्रेस जवन की सिकंदराबाद और बॉम्बे वीटी (अब सीएसटीएम) की बीच चले, ओकरी साथ आपन रेक साझा करे। ए रेल क शुरूआत में नेवी ब्लू बाहरी रंग सज्जा रहल, और एकर रेक साझाकरण की व्यवस्था की कारण एकरी खिड़कियन प एगो सफेद पट्टी रहे। बॉम्बे आ [[भुवनेश्वर]] की बीच्चे एगो विशेष रेलगाड़ी की मांग में वृद्धि की साथ, ए ट्रेन के बॉम्बे वी.टी. ले सन 1994 में बढ़ा दिहल गइल रहे जेसे 2101/2102 मिनार एक्सप्रेस (जवने क बाद में 2701/02 हुसैनसागर एक्स्प्रेस जगह लिहलस) की साथ रेक साझाकरण क व्यवस्था समाप्त हो गइल। विस्तार के बाद ए ट्रेन के 1994 में 1019/1020 नया नंबर दिहल गइल। इ देश की पूर्वी और पश्चिमी राज्यन के जोड़े वाली सबसे पुरान कईगो ट्रेन में से एगो ह। वर्तमान में इ 11019/11020 के रूप में कार्य करतीया और इ मुंबई छत्रपति शिवाजी टर्मिनस (सीएसटीएम) के भुवनेश्वर से जोड़ेले। ==रास्ता== दादर, कल्याण, लोनावाला, [[पुणे]], दौंड, [[सोलापुर]], [[गुलबर्गा]], तंदूर, [[सिकंदराबाद]], काजीपेट, वारंगल, महबूबबाद, विजयवाड़ा, एलुरु, राजमुंड्री, विशाखापट्टनम, श्रीकाकुलम, पलासा और बेरहमपुर और खुर्दा रोड ए गाड़ी की रास्ता में प्रमुख हाल्ट हंव स।<ref>http://indiarailinfo.com/train/timetable/konark-express-11020-bbs-to-cstm/946/238/1620</ref><ref>{{cite web| url=https://www.cleartrip.com/trains/11020| publisher=cleartrip.com| title=Konark Express train time table| accessdate=7 July 2017| archive-date=5 April 2016| archive-url=https://web.archive.org/web/20160405232315/http://www.cleartrip.com/trains/11020| url-status=dead}}</ref> एकर यात्रा क कुल दूरी 1932 किमी ह एमें पूर्व [[आंध्र प्रदेश]] (वर्तमान तेलंगाना और आंध्र प्रदेश) राज्य में लगभग 1066 किलोमीटर क दूरी शामिल बा। ==कोच संरचना== एमें 1गो एसी 2 टीयर, 5गो एसी 3 टीयर, 12गो स्लीपर, 1गो पैंट्री कार, 2गो अनारक्षित और 2गो ब्रेक-कम-गार्ड वैन हो न स, एमें कुल मिला के 23गो कोच होन स। और एकर रख रखाव ईस्ट कोस्ट रेलवे और मध्य रेलवे क्षेत्र द्वारा कइल जाला।<ref>{{cite web| url=https://erailway.co.in/trains/11020-KONARK-EXPRESS| publisher=erailway.co.in| title=Konark Express Coach Position| accessdate=7 July 2017}}{{Dead link|date=October 2023 |bot=InternetArchiveBot |fix-attempted=yes }}</ref> ==रेल इंजन== एकरी रास्ता में खाली कुछ भाग क विद्युतीकरण होखले की कारण, एके दुगो लोकोमोटिव दिहल गइल बा। [[मुंबई]] सीएसटी और विजयवाड़ा जंक्शन की बीच्चे, एमें कल्याण शेड क डब्लूपीपी-4 डी लागेला और विजयवाडा जंक्शन से भुवनेश्वर रेलवे स्टेशन ले लालगुडा शेड क डब्लूएपी-4 लागेला। ==घटना सभ== 31 जुलाई 2008 के कोनार्क एक्सप्रेस क इंजन और दुगो कोच गुलबर्गा की पास गंगापुर स्टेशन पर पटरी से उतर गईन स जेमें दू लोग घायल हो गइल। पटरी से उतरले क कारण अबले मालूम नाहीं चलल बा।<ref>{{cite web|title=Konark Express derails, two people injured|url=http://news.oneindia.in/2008/07/31/konark-express-derails-two-injured-1217496995.html|publisher=OneIndia News|accessdate=25 July 2011|archive-date=9 October 2012|archive-url=https://web.archive.org/web/20121009155026/http://news.oneindia.in/2008/07/31/konark-express-derails-two-injured-1217496995.html|url-status=dead}}</ref> ===फोटो गैलरी=== <gallery> File:Konarkexpress.JPG|Konark express(11020) at Kazipet File:WDP4D 05112016.jpg|thumb|A WDP4D hauling Bhubaneshwar-bound Konark Express near Secunderabad File:Konark Exp at Moulaali1.jpg|Konark Exp at Moulaali File:11020 Konark Express.jpg|11020 Konark Express </gallery> ==संदर्भ== <references/> [[श्रेणी:भारतीय रेल के गाड़ी]] kk9wr0mmw0k2aksjrqflqvgh5ozk3t9 जगजीत सिंह 0 47981 803294 801919 2026-07-29T16:06:24Z SM7Bot 7808 /* top */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803294 wikitext text/x-wiki {{Infobox person | name = जगजीत सिंह | image = Jagjit Singh (Ghazal Maestro).jpg | caption = साल 2003 में भुबनेश्वर में एगो कार्यक्रम के दौरान जगजीत सिंह | birth_name = जगमोहन सिंह धीमन | birth_date = {{Birth date|df=yes|1941|2|8}} | birth_place = श्री गंगानगर, बीकानेर राज, राजपुताना एजेंसी, ब्रिटिश भारत | death_date = {{Death date and age|df=yes|2011|10|10|1941|2|8}} | death_place = मुंबई, महाराष्ट्र | occupation = गायक, संगीत निर्देशक | spouse = {{marriage|चित्रा सिंह|1969}} | children = विवेक | module= {{Infobox musical artist | embed=yes | background = solo_singer | genre = ग़ज़ल, शास्त्रीय, भक्ती, लोक संगीत | instrument = गला, हारमोनियम, तानपूरा, पियानो | years_active = 1961–2011 | label = ईएमआइ, एचएमवी, सारेगामा, यूनिवर्सल, सोनी बीएमजी, सीबीएस, पौलीडोर, टिप्स, वीनस, टी-सीरीज, मैग्ना साउंड, बिग, टाइम्स | website = | associated_acts= }} | alias = गजल सम्राट }} '''जगजीत सिंह''' (8 फ़रवरी 1941 - 10 अक्टूबर 2011) क नांव बेहद लोकप्रिय ग़ज़ल गायकन में शुमार कइल जाला। उन क संगीत अंत्यंत मधुर रहल और उन क आवाज़ संगीत की साथ खूबसूरती से घुल-मिल जाला। खालिस उर्दू जाने वालन क मिल्कियत समझल जाए वाली, नवाबन की दुनिया में झनके वाला और शायरन की महफ़िलन में वाह-वाह की दाद प इतराए वाली ग़ज़लन के आम आदमी तक पहुंचवले क श्रेय अगर केहू के पहिले पहल देवे के होखे त जगजीत सिंह क ही नांव ज़बान प आवेला। उन क ग़ज़ल न सिर्फ़ उर्दू की कम जानकारन की बीच शेरो-शायरी की समझ में इज़ाफ़ा कइली स बल्कि ग़ालिब, मीर, मजाज़, जोश और फ़िराक़ जैसन शायरन से भी उन क परिचय करवली स।  जगजीत सिंह के सन 2003 में भारत सरकार द्वारा कला की क्षेत्र में पद्म भूषण से सम्मानित कइल गइल। फरवरी 2014 में इनकी सम्मान व स्मृति में दुगो डाक टिकट भी जारी कइल गइल।<ref>{{cite web|title=PM Manmohan Singh releases Stamp to Honor Jagjit Singh|url=http://news.biharprabha.com/2014/02/pm-manmohan-singh-releases-stamp-to-honor-jagjit-singh/|work=IANS|publisher=news.biharprabha.com|accessdate=8 February 2014|archive-date=14 November 2014|archive-url=https://web.archive.org/web/20141114111946/http://news.biharprabha.com/2014/02/pm-manmohan-singh-releases-stamp-to-honor-jagjit-singh/|url-status=dead}}</ref> ==आरंभिक दिन== [[File:Shahid Kabir with Jagjit Singh and his son, Sameer Kabeer.jpg|thumb|Jagjit Singh (middle) with poet Shahid Kabir and his son, Sameer Kabeer]] जगजीत जी क जन्म 8 फरवरी 1941  (मृत्यु 10 ऑक्टोबर 2011) के  [[राजस्थान]] की गंगानगर में भइल रहे। पिता सरदार अमर सिंह धमानी भारत सरकार क कर्मचारी रह न। जगजीत जी क परिवार मूलतः [[पंजाब]] (भारत) की  रोपड़ ज़िला की दल्ला गांव क रहे वाला ह।<ref>http://www.firstpost.com/living/the-guilty-pleasure-of-jagjit-singh-indias-ghazal-king-103483.html</ref> मां बच्चन कौर पंजाब क ही समरल्ला की उट्टालन गांव क रहे वाला रहली। <ref name=TOI20021110>{{cite news |last=Sawhney |first=Anubha |title=Unforgettable moments with Jagjit Singh|url=http://timesofindia.indiatimes.com/articleshow/27885263.cms|accessdate=11 January 2012|newspaper=Times of India|date=10 November 2002}}</ref> जगजीत क बचपन का नांव जीत रहे। करोड़ों सुने वाला श्रोता लोगन की चलते सिंह साहब कुछ ही दशक में जग के जीते वाला जगजीत बन गई न। शुरूआती शिक्षा गंगानगर की खालसा स्कूल में भइल और बाद में पढ़े खातिर जालंधर आ गई न। डीएवी कॉलेज से स्नातक क डिग्री लीह न और एकरी बाद कुरूक्षेत्र विश्वविद्यालय से इतिहास में पोस्ट ग्रेजुएशन कई न।<ref name=Guardian20111025>{{cite news |newspaper=The Guardian |location=London |title=Jagjit Singh obituary |first=Asjad |last=Nazir |date=25 October 2011 |url=https://www.theguardian.com/music/2011/oct/25/jagjit-singh-obituary |accessdate=11 January 2012}}</ref> इन क पढ़ाई में दिलचस्पी कम रहे। कई क्लास मे त दू- दू साल गुज़रल। एक बेर आपन चचेरी बोहीन की शादी में जमल महिला मंडली की बैठक में जाके गीत गावे लग न। पूछले प कहें कि सिंगर नहीं बनती त धोबी होतीं। पिता के इजाज़त की बग़ैर फ़िल्म देखल और टाकीज में गेट कीपर के घूस देके हॉल में घुसल आदत रहे। ==संगीत क सफ़र== बचपन में अपनी पिताजी से संगीत विरासत में मिलल रहे। गंगानगर में ही पंडित छगन लाल शर्मा की सानिध्य में दू साल ले शास्त्रीय संगीत सीखले क शुरूआत कई न। आगे जाकर सैनिया घराना की उस्ताद जमाल ख़ान साहब से ख्याल, ठुमरी और ध्रुपद क बारीकी सीख न। पिता क ख़्वाहिश रहे कि उन क बेटा भारतीय प्रशासनिक सेवा (आईएएस) में जाए लेकिन जगजीत जी प त गायक बनले क धुन सवार रहे। कुरुक्षेत्र विश्वविद्यालय में पढ़ाई की दौरान संगीत में उन क दिलचस्पी देख के कुलपति प्रोफ़ेसर सूरजभान जगजीत सिंह जी के बहुत उत्साहित कई न। उनहीं की कहले प उ 1965 में [[मुंबई]] आ गई न।<ref name=Guardian20111025 /> एही जा से संघर्ष क दौर शुरू भइल। उ पेइंग गेस्ट की तौर पर रहें और विज्ञापनन खातिर जिंगल्स गाके या शादी-समारोह वगैरह में गाके रोज़ी रोटी का जुगाड़ करें। 1967 में जगजीत जी क मुलाक़ात चित्रा जी से भइल। दू साल बाद दुनुं 1969 में परिणय सूत्र में बंध गइल लो।.<ref name="TOI20021110"/> {{clear}} ==संदर्भ== {{reflist|33em}} ==बाहरी कड़ी== {{Commons category|Jagjit Singh}} * [http://www.bbc.co.uk/music/artists/ff7d390f-03be-40e4-9210-3e0f660966df Jagjit Singh on BBC] [[श्रेणी:सिनेमा]] [[श्रेणी:भारतीय गायक]] ff849q9g4xns6xkkc9ayr1n61r1legq एयर चाइना 0 48301 803352 773184 2026-07-29T16:22:55Z SM7Bot 7808 /* इतिहास */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803352 wikitext text/x-wiki '''''एयर चाइना लिमिटेड''''', [[चीन|पीपुल्स रिपब्लिक ऑफ चाइना]] क ध्वजवाहक और एकर कईगो प्रमुख एयरलाइंस में से एगो ह, जेकर मुख्यालय [[बीजिंग]] में शुनाय जिला ह। एयर चाइना क उड़ान संचालन केंद्र बीजिंग कैपिटल इंटरनेशनल एयरपोर्ट पर आइल बा।<ref>{{cite web|title=एयर चाइना के विषय में |url=http://www.staralliance.com/en/about/airlines/air-china_airlines/|publisher=staralliance.com|accessdate=24 July 2017}}</ref> सन 2015 में, इ एयरलाइन 90 मिलियन घरेलू और अंतरराष्ट्रीय यात्रिन के 80% के औसत लोड फैक्टर के साथ गंतव्य प पहुँचवलस।<ref>{{cite web|title=एयर चाइना वार्षिक रिपोर्ट 2015|url=http://www.airchina.com.cn/en/images/investor_relations/2016/03/31/E993685CDB67732B19075FBAF25FAD0D.pdf|publisher=airchina.com.cn|accessdate=24 July 2017}}</ref> ==इतिहास== [[File:AirChinaBuildingShunyi1.JPG|thumb|एयर चाइना HQ Building]] एयर चाइना क स्थापना और संचालन क शुरुआत 1 जुलाई 1988 के 1987 में चीनी सरकार की एगो फैसला की परिणामस्वरूप भइल रहे जवने की अनुसार चीन क नागरिक विमानन प्रशासन (सीएएसी) की सब ऑपरेटिंग डिवीजन क निम्नलिखित छौगो अलग-अलग एयरलाइंस में विभाजन करा गइल रहे: एयर चाइना, चाइना ईस्टर्न, चाइना साउथर्न, चाइना नॉर्थन, चाइना साउथ वेस्ट, और चाइना नॉर्थवेस्ट। एयर चाइना के इंटरकॉन्टिनेंटल फ्लाइट्स क मुख्य जिम्मेदारी दिहल गइल और सीएएसी क लंबी दूरी वाला विमान (बोइंग 747 एस, 767 एस और 707 एस) और दूर क मार्गन के अधिग्रहित क लिहलस।<ref>{{cite web|title=एयर चाइना 15 नए विमान खरीदने की योजना बना रही है|url=http://www.seattlepi.com/business/article/Air-China-plans-to-buy-15-new-planes-1099632.php|publisher=seattlepi.com|accessdate=24 July 2017}}</ref><ref>{{cite web|title=एयर चाइना एयरलाइंस|url=https://www.cleartrip.com/flight-booking/air-china-airlines.html|publisher=cleartrip.com|accessdate=24 July 2017}}</ref> जनवरी 2001 में, पूर्व सीएएसी क दसगो एयरलाइंस विलय योजना खातिर सहमत भईन स, जेकरी अनुसार एयर चाइना के चीन साउथवेस्ट एयरलाइंस क अधिग्रहण करे के रहे। अधिग्रहण से पहिले, एयर चाइना देश क चौथा सबसे बड़ा घरेलू एयरलाइन रहे और ए विलय से 118 विमानन क एगो बेड़ा की साथ 56 अरब युआन (USD $ 8.63 बिलियन) क संपत्ति क एगो समूह बनल। अक्टूबर 2002 में, एयर चाइना चीन की राष्ट्रीय विमानन होल्डिंग और चीन साउथवेस्ट एयरलाइंस की साथ समेकित हो गइल। 15 दिसंबर 2004 के, एयर चाइना के [[हांगकांग]] और लंदन स्टॉक एक्सचेंज प सफलतापूर्वक सूचीबद्ध कइल गइल। 2006 में, एयर चाइना स्टार अलायंस में शामिल होखे खातिर एगो समझौता प हस्ताक्षर कइलस और इ 12 दिसंबर, 2007 के शंघाई एयरलाइंस की साथ गठबंधन क सदस्य बन गइल। जुलाई 2009 में, एयर चाइना अपनी परेशानी से घिरल सहायक एयर मकाऊ से 19.3 मिलियन अंश क अधिग्रहण कइलस, जेसे ए कैरियर में एकर हिस्सेदारी 51% से बढ़कर 80.9% हो गइल। एक महीना बाद, एयर चाइना हांगकांग में आपन उपस्थिति क विस्तार कइले की इरादा से, कैथै प्रशांत में आपन हिस्सेदारी 17.5% से बढ़ाकर 30% तक करे खातिर 6.3 अरब डॉलर (यूएस $ 813 मिलियन) खर्च कइलस। अप्रैल 2010 में, एयर चाइना शेन्ज़ेन एयरलाइंस में शेयरहोल्डिंग क वृद्धि पूरा कके शेन्ज़ेन एयरलाइंस क कंट्रोलिंग शेयरधारक बन गइल और एकरी चलते एयर चाइना बीजिंग, चेंगदू, और शंघाई में आपन स्थिति मजबूत कइलस और आपन घरेलू नेटवर्क में और संतुलन हासिल कइलस।<ref>{{cite web|title=एयर चाइना शीर्ष स्पेनिश पर्यटन पुरस्कार प्राप्त करता है |url=http://www.eturbonews.com/19875/air-china-receives-top-spanish-tourism-award|publisher=eturbonews.com|accessdate=24 July 2017}}</ref> 2 दिसंबर 2010 के, एयर चाइना स्पेन क सर्वोच्च पर्यटन उद्योग पुरस्कार प्राप्त कइलस, और इ पुरस्कार प्राप्त करे वाला एयर चाइना पहिला विदेशी एयरलाइन बनल, आमतौर प इ पुरस्कार खाली एइसन संगठन और व्यक्ति लोगन के दिहल जा जवन की स्पेनिश पर्यटन उद्योग में कौनों तरह क योगदान दें स। 23 दिसंबर 2010 के, एयर चाइना ओ संयुक्त टिकटन क पेशकश करे वाली पहिला चीनी एयरलाइन बन गइल, जेमें पास की शहरन में घरेलू उड़ान और शटल बस सेवा शामिल रहे।<ref>{{cite web|title=एयर चाइना घरेलू मार्गों के लिए उड़ान शटल बस संयुक्त टिकट की शुरूआत करती है|url=http://www.smartinternchina.com/air-china-press-release|publisher=smartinternchina.com|accessdate=24 July 2017|archive-date=4 April 2016|archive-url=https://web.archive.org/web/20160404171143/http://www.smartinternchina.com/air-china-press-release|url-status=dead}}</ref> एयर चाइना 15 नवंबर, 2011 के अपनी विमानन में वाई-फाई इंटरनेट सेवा दिहल शुरू क दिहलस, जेसे इ ए तरह क सेवा क पेशकश करे वाली पहिला चीनी वाहक बन गइल। ==इहो देखल जाय== * [[एयर इंडिया]] ==संदर्भ== {{reflist}} [[श्रेणी:हवाई जहाज सेवा]] q5da004yk7d16nptlxpuhw33hhbxvhf बोगमालो बीच रिसोर्ट 0 48366 803339 782531 2026-07-29T16:21:57Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803339 wikitext text/x-wiki '''बोगमालो बीच रिसोर्ट''', [[गोवा]] की दक्षिणी छोर पर, बोगमालो बीच पर, स्थित एगो पाँच-सितारा होटल ह। प्राकृतिक रूप से बनल ई बीच, अर्धचंद्र आकार क बा आ पश्चिमी तटीय मैदान क सबसे व्यस्तम समुद्र तट में से एगो ह। इ रिसोर्ट किनारे पर चले वाला कसीनो, जेकर संचालन ट्रेड विंग्स होटल लिमिटेड की निदेशक द्वारा होला, नॉटिलस रेस्तरां, 126 डीलक्स और सेमी-डीलक्स कमरा और एकरी पीछे मौजूद एगो स्विमिंग पूल की साथ गर्व से खड़ा बा।<ref>{{cite web|title=official website|url=http://www.bogmallobeachresort.com/aboutus.php|publisher=bogmallobeachresort.com}}{{Dead link|date=March 2025 |bot=InternetArchiveBot |fix-attempted=yes }}</ref> मुख्य इमारत द्वारा घेरल क्षेत्र लगभग 1,332.14 वर्ग मीटर (14,338.98 फीट<sup>2</sup>) बा आ इमारत की सामने क भाग 518.24 वर्ग मीटर (5,578.2 9 फीट<sup>2</sup>) में बनावल गइल बा। दक्षिण में पूल 194.57 वर्ग मीटर क्षेत्र (2,094.31 फीट<sup>2</sup>) क क्षेत्र घेरेला। ==इतिहास== एकर स्वामित्व ओबेराय समूह की लगे बा और एके पहिले होटल ओबेराय के नांव से जानल जा, जेकर बाद में बोगमालो  बीच की ऊपर से पुनः नामकरण कइल गइल। द लिंक बैंड क सह-संस्थापक झिस्टो पेस याद करत बतावे न की प्रसिद्ध पॉप गायक और गिटारवादक स्वर्गीय टेरेंस वाज़, जवन की बैंड 'लिंक्स' क सदस्य रह न, ऊ ओबेराय रिसोर्ट में नियमित रूप से गिटार बजावें, ऊ वर्ष 1974 में ए बैंड से जुड़ न और 1994 तक बजवन।<ref>{{cite web|title=बोगमालो बीच रिसोर्ट|url=https://www.cleartrip.com/hotels/info/bogmallo-beach-resort-41661|publisher=cleartrip.com}}</ref> ==मीडिया== 27 अप्रैल 2015 के वास्को पुलिस विभाग तटवर्ती कैसीनो के सील क दिहलस काहें की ए रिसॉर्ट क 5 सितारा लाइसेंस समाप्त हो गइल रहे और इ कैसीनो के गैर-कानूनी रूप से संचालित करत रहे। इ रिसोर्ट नलकॉन 2015 क मेजबानी कइलस, जवन की 4 फरवरी और 5 फरवरी के [[एंड्रॉइड]] सुरक्षा खामी और ओकर फायदा उठवले में रुचि रखे वाला लोगन खातिर 2 दिन अनंत श्रीवास्तव और आदित्य गुप्ता द्वारा आयोजित कइल गइल रहे। ==संदर्भ== {{reflist}} [[श्रेणी:होटल]] [[श्रेणी:गोवा के होटल]] 72bs41r2ywlrfo1zjmx7fax2eg8f5ia तिरुपति रेलवे स्टेशन 0 48916 803342 762640 2026-07-29T16:22:09Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803342 wikitext text/x-wiki {{Infobox station | name = तिरुपति | native_name = | native_name_lang= te | type = एक्सप्रेस ट्रेन, यात्री ट्रेन तथा कम्यूटर रेल स्टेशन | style = Indian railway | image = TPTY Name Board.jpg | image_caption = तिरुपति रेलवे स्टेशन | address = चेन्नई-अनंतपुर उच्च मार्ग 205, तिरुपति,आंध्र प्रदेश | country = भारत | coordinates = {{Coord|13.6279|N|79.4194|E|type:railwaystation_region:IN|format=dms|display=inline,title}} | elevation = {{convert|150|m|ft|0|abbr=on}} | line = गुडुर-काटपाडी शाखा लाइन | other = | structure = Standard (on ground station) | platform = 5 | tracks = 8 | parking = Available | closed = | rebuilt = | electrified = Yes | ADA = | code = Indian railway code | owned = | operator = | status = Functioning | former = | passengers = | pass_year = | pass_percent = | pass_system = }} '''तिरुपति रेलवे स्टेशन''' (स्टेशन कोड: TPTY) भारत की आंध्र प्रदेश राज्य में स्थित रेलवे स्टेशन ह, इ तिरुपति शहर और बड़ी संख्या में चित्तूर जिला की तिरुमला वेंकटेश्वर मंदिर की दर्शन खातिर आवे वाला यात्रीन की नियमित प्रवाह के सेवा देला।<ref>{{cite web|title=Station Code Index|url=http://www.indianrailways.gov.in/railwayboard/uploads/directorate/coaching/TAG_2015-16/Station_Code(1).pdf|publisher=Portal of Indian Railways|accessdate=14 Aug 2017|page=2|format=PDF}}</ref> ==इतिहास== दक्षिण भारतीय रेलवे में एगो मीटर गेज लाइन, दक्षिण आरकॉट जिला में विल्लुपुरम से 1891 में शुरू भइल और काटपाड़ी और चित्तूर से होक पकाला तक गइल। काटपाडी-गुडुर लाइन, जवन तिरुपति के कवर करेले, अब इ ब्रॉड गेज करा गइल बा।<ref>{{cite web|title=Katpadi Jn. - Pakala Jn|url=http://www.irfca.org/articles/metric/kpd.html|publisher=irfca.or|accessdate=14 Aug 2017}}</ref> ==वर्गीकरण== तिरुपति के गुंटकाल रेलवे डिवीजन में ए 1 श्रेणी की स्टेशन की रूप में वर्गीकृत कइल गइल रहे। इ भारतीय रेलवे की शीर्ष सौ बुकिंग स्टेशन में से एगो ह।<ref>{{cite web|title=Indian Railways Passenger Reservation Enquiry|url=http://www.indianrail.gov.in/7days_Avl.html|publisher=IRFCA.|accessdate=14 Aug 2017|archive-date=10 May 2014|archive-url=https://web.archive.org/web/20140510115649/http://www.indianrail.gov.in/7days_Avl.html|url-status=bot: unknown}}</ref><ref>{{cite web|title=Category of Stations over Guntakal Division|url=http://www.scr.indianrailways.gov.in/view_section.jsp?lang=0&id=0,1,291,353,1280,1349|publisher=South Central Railway zone. Portal of Indian Railways|accessdate=14 Aug 2017}}</ref> ==राजस्व== नीचे दिहल गइल तालिका पिछला सालन क स्टेशन क यात्री आय दर्शावतीया।<ref>{{cite web|title=Tirupati Train Station|url=https://www.cleartrip.com/trains/stations/TPTY|publisher=cleartrip.com|accessdate=14 Aug 2017}}</ref> {| class="wikitable" border="1" cellpadding="6" style="border-collapse: collapse;text-align:center" |+ !साल !राजस्व (लाख में) |- | 2011–12 | 15536.77 |- |2012–13 |16973.58 |- |2013–14 |19787.68 |- |2014–15 |23226.40 |} ==उपलब्ध सुविधा== तिरुपति रेलवे स्टेशन प 5गो प्लेटफार्म बान स और एमें से हरेक 24 से अधिक कोच वाला ट्रेन के संभाल सकेला। प्रत्येक प्लेटफार्म पर एस्केलेटर उपलब्ध बान स। तिरुपति रेलवे स्टेशन प्रति दिन 45 जोड़ा से अधिक पैसेंजर ट्रेन के संभालेला। ==सन्दर्भ== {{reflist}} [[श्रेणी:भारतीय रेल]] 259lycndnxyqfrxs7lkr6iev0aqpud8 सिंगापुर एयरलाइंस 0 48932 803295 773182 2026-07-29T16:06:31Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803295 wikitext text/x-wiki {{Infobox airline |airline=सिंगापुर एयरलाइंस |logo= |logo_size=200 |fleet_size=सिंगापुर एयरलाइंस बेड़ा|111 |destinations=सिंगापुर एयरलाइंस स्थल|64 |IATA=SQ |ICAO=SIA |callsign=सिंगापुर |company_slogan=A Great Way to Fly |founded={{Start date and years ago|1947|05|01|df=y}} (as Malaysia–सिंगापुर एयरलाइंस) |commenced= {{nowrap|{{Start date and years ago|1972|10|01|df=y}}}} |headquarters=Airline House<br />25 Airline Road<br />Singapore 819829<br />सिंगापुर |parent=Temasek Holdings (56%) |key_people= <div> * Peter Seah Lim Huat <br> {{small|(Chairman)}} * Goh Choon Phong <br> {{small|(Chief Executive Officer)}} |hubs=सिंगापुर चंगी एयरपोर्ट |frequent_flyer= * KrisFlyer * PPS Club |alliance=स्टार अलायन्स |subsidiaries= [[Budget Aviation Holdings]]<ref>{{cite web|author=आनंद कृष्णमूर्ति आनंदकोमोरथी |url=https://www.bloomberg.com/news/articles/2016-05-18/singapore-air-forms-new-company-to-manage-two-budget-carriers |title=सिंगापुर एयर फॉर्म्स नई कंपनी दो बजट वाहक प्रबंधित |publisher=ब्लूमबर्ग |date=2016-05-17 |accessdate=2016-06-26}}</ref> <br />SilkAir<br />[[सिंगापुर Airlines Cargo]]<br />SIA Engineering Company |net_income = {{increase}} [[Singapore dollar|S$]]804.4&nbsp;million <small>(FY 2015/16)</small> |num_employees = 24,574 <small>(FY 2015/16)</small> |website= {{URL|https://www.singaporeair.com|singaporeair.com}} }} '''सिंगापुर एयरलाइंस''' लिमिटेड (एसआईए)  सिंगापुर क ध्वज वाहक ह। इ विश्व क एगो प्रमुख वायुयान सेवा ह। सिंगापुर क चंगी हवाई अड्डा एकर हब (महत्वपूर्ण गतिविधिन क केंद्र) ह और दक्षिणपूर्वी एशिया, पूर्वी एशिया, [[दक्षिण एशिया]] और "कंगारू रूट" बाजारन में एकर एगो सक्षम उपस्थिति बा। इ कम्पनी पार-प्रशांत (ट्रांस-पेसिफिक) उड़ान क भी संचालन करेले, एमे दुनिया क दुगो सबसे लम्बा नॉन-स्टॉप व्यावसायिक उड़ान शामिल बाड़ी स, इ उड़ान एयरबस ए340-500 सिंगापुर से न्यू यॉर्क और लोस एंजिल्स तक जाली स।<ref>{{cite web|title=Singapore Girl - You're a Great Way To Fly".|url=http://www.singaporeair.com/en_UK/in/flying-withus/our-story/singapore-girl/|publisher=singaporeair.com Retrieved 24 April 2015}}</ref> सिंगापुर एयरलाइंस समूह में कईगो एयरलाइन-संबंधित सहायक कंपनी शामिल बाड़ी स, एसआईए इंजीनियरिंग कंपनी नौ देशन में रखरखाव, मरम्मत और ओवरहाल (एमआरओ) कारोबार क संचालन करेले, जेमें बोइंग और रोल्स-रॉयस सहित 27 संयुक्त उद्यमन क पोर्टफोलियो बा। सिंगापुर एयरलाइंस कार्गो एसआईए की मालवाहक बेड़ा क संचालन करेला और एकर यात्री विमानन में मालवाहक क्षमता क प्रबंधन करेला। एकर दुगो सहायक बान स: सिल्कएयर द्वितीयक शहरन में क्षेत्रीय उड़ानन क संचालन करेला, जबकि स्कूट कम लागत वाला वाहक की रूप में काम करेला।<ref>{{cite web|title=एसआईए इंजीनियरिंग कंपनी बोइंग के साथ संयुक्त उद्यम को शामिल करती है|url=http://www.straitstimes.com/business/companies-markets/sia-engineering-company-incorporates-joint-venture-with-boeing|publisher=straitstimes.com}}</ref><br> [[File:SIA Boeing 747-400, 9V-SPP, SIN 6 for wiki.jpg|thumb|Boeing 747-400 at सिंगापुर चंगी एयरपोर्ट इन स्टार अलायन्स लिवेरय व्हिले स्टिल मैनटैनिंग इतस कॉर्पोरेट लोगो ऑन द टेल|248x248px]] सिंगापुर एयरलाइंस "सुपरजम्बो" एयरबस ए380 क शुरुआत कइलस जवन विश्व क सबसे बड़ा एयरक्राफ्ट रहे। राजस्व प्रति यात्री किलोमीटर की मामला में इ दुनिया भर की चोटी की 15 वाहकन में से एगो ह, और अंतर्राष्ट्रीय यात्री वहन की दृष्टि से दुनिया में दसवां स्थान पर बा। सिंगापुर एयरलाइंस उ एयरलाइन ह जेकर दुनिया में सबसे ज्यादा प्रशंसा कइल जाला और फोर्च्यून वर्ल्ड की 2010 की सबसे प्रशंसित कम्पनियन की रैंकिंग में एकर 27 वां स्थान बा।<ref>{{cite web|url=https://www.cleartrip.com/flight-booking/singapore-airlines.html|title=सिंगापुर एयरलाइंस|publisher=क्लेअरट्रिप.कॉम }}</ref><ref>{{cite web|title=एयरलाइन स्पॉटलाइट: सिंगापुर एयरलाइनs|url=http://www.flightnetwork.com/blog/spotlight-singapore-airlines/|publisher=flightnetwork.com|access-date=2017-08-16|archive-date=2016-04-04|archive-url=https://web.archive.org/web/20160404215802/http://www.flightnetwork.com/blog/spotlight-singapore-airlines/|url-status=dead}}</ref> एकर सशक्त ब्रांड नांव बा और इ विमानन उद्योग में, विशेष रूप से नवाचार, सुरक्षा और सेवा में उत्कृष्टता की दृष्टि से उदाहरण प्रस्तुत करेले,  साथ ही एके निरंतर लाभ भी प्राप्त होता। एके असंख्य पुरस्कार मिलल बा और विमान की खरीद की उद्योग में इ प्रथम स्थान पर बा। इ उन छह एयरलाइंस में से एगो ह जेके केथे पेसिफिक (Cathay Pacific), एशियन एयरलाइंस (Asian Airlines), मलेशियन एयरलाइंस (Malaysia Airlines), क़तर एयरवेज़ (Qatar Airways) और किंग्फिशर एयरलाइंस (Kingfisher Airlines) की साथ स्काईट्रेक्स की द्वारा 5 स्टार रेटिंग से सम्मानित कइल गइल बा। ==इतिहास== सिंगापुर एयरलाइंस क शुरुआत 12 अक्टूबर 1937 के मलायी एयरलाइंस (एमएएल) की समावेश की साथ भइल। एकर शुरुआत लिवरपूल क ओशीन स्टीमशिप कम्पनी, सिंगापुर क स्ट्रेट्स स्टीमशिप कम्पनी और इम्पीरियल एयरवेज़ की द्वारा कइल गइल। ए एयरलाइन क पहिला उड़ान एगो चार्टर्ड उड़ान रहे जवन 2 अप्रैल 1947 के सिंगापुर की ब्रिटिश स्ट्रेट्स सेटलमेंट से कुआलालम्पुर तक गइल, एमें एयरस्पीड कौंसुल दुगो इंजन वाला एयरप्लेन क उपयोग कइल गइल।  एकरी तुरंत बाद 1 मई 1947 से नियमित साप्ताहिक उड़ान सिंगापुर से कुआलालम्पुर, इपोह और पेनांग जाए लगली स, एमें एही तरह की एयरक्राफ्ट क उपयोग कइल जाव।  1940 की शेष दशक में और 1950 की दशक की दौरान अन्य ब्रिटिश कॉमनवेल्थ एयरलाइंस (जैसे की बीओएसी और क्वान्टास एम्पायर एयरवेस) की तरह इ एयरलाइन निरंतर विस्तृत होत गइल, इ तकनीकी सहायता प्रदान कइलस और आईएटीए में शामिल होखले में मदद कइलस। 1955 ले, मलायी एयरवेज क बेड़ा एतना विकसित हो गइल रहन स कि एमें बड़ी संख्या में डगलस डी सी-3 (Douglas DC-3s) शामिल हो चुकल रह न स। ==संदर्भ== [[श्रेणी:हवाई जहाज]] [[श्रेणी:हवाई जहाज सेवा]] o59geh03lj2749prpv1qmqbhdk68vvo आइस क्लाइम्बिंग 0 48943 803293 762341 2026-07-29T16:06:20Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803293 wikitext text/x-wiki {{italic title}} [[File:Zinal ice climbing.webm|thumb|आइस क्लाइम्बिंग]] '''''आइस क्लाइम्बिंग''''' (बर्फ चढ़ाई) [[पहाड़]]न पर बर्फ संरचना प चढ़ाई क गतिविधि ह। आमतौर पर, बर्फ पर चढ़ाई क मतलब होला बर्फबारी, जम्मल झरना, और जम्मल बर्फ से ढँकल चट्टानन और पत्थरन की स्लैब पर सुरक्षा और रस्सी जैसन सुविधा की साथ चढ़ाई|<ref>{{cite book |last=Chouinard |first=Yvon |title=Climbing Ice |publisher=Sierra Club Books |location=San Francisco |year=1978 |isbn= 978-0871562081}}</ref> चढ़ाई की उद्देश्यन खातिर, बर्फ के मोटे तौर पर दुगो क्षेत्र, अल्पाइन बर्फ और पानी क बर्फ में विभाजित कइल जा सकेला। अल्पाइन बर्फ पहाड़ी वातावरण में मिलेला, आमतौर पर चोटी पर पहुंचे खातिर एगो दृष्टिकोण क आवश्यकता रहेला, और अक्सर पर्वत की शिखर प एक ही प्रयास में चढ़ल जाला। पानी क बर्फ आमतौर पर चट्टान पर मिलेला या पानी के प्रवाह के नीचे रहेला। अल्पाइन बर्फ तेजी से जम जाला, जबकि पानी क बर्फ जम्मल पानी क एगो तरल प्रवाह ह।<ref name = "lowe">{{cite book |last=Lowe |first=Jeff |title=Ice World: Techniques and Experiences of Modern Ice Climbing |publisher=The Mountaineers |location=Seattle |year=1996}}</ref><ref>{{cite book |edition=First |publisher=Mountaineers Books |isbn=0-89886-769-X |last=Gadd |first=Will |author2=Roger Chayer |title=Ice & Mixed Climbing: Modern Technique |date=November 2003}}</ref> ==तकनीक== बर्फ की ढलान और बनावट की अनुसार पर्वतारोही उपकरण चुनेला लो। उदाहरण खातिर, समतल बर्फ प, लगभग कौनों अच्छा लंबा पैदल यात्रा या पर्वतारोहण वाला बूट आमतौर पर पर्याप्त होला, लेकिन गंभीर प्रवृति की बर्फीला पर्वतारोहण खातिर डबल प्लास्टिक वाला जूता या ओईसने कठोर चमड़ा क जूता आमतौर पर इस्तेमाल कइल जाला। पतला या संकरा ढलानन प,बर्फ काट के रास्ता बनावे खातिर बर्फ क कुल्हाड़ी क उपयोग कइल जा सकेला। लंबा और तेज ढलान या ग्लेशियर यात्रा की सुरक्षित चढ़ाई की खातिर क्रैम्पन अनिवार्य होला। खड़ा बर्फ चढ़ाई क्रैम्पन और बर्फ क कुल्हाड़ी की साथ कइल जाला; पर्वतारोही बर्फ में क्रैम्पन के फंसावे खातिर पैर के बर्फ में जोर से किक करे न स और फिर कुल्हाड़ी अपने सिर के ऊपर से घुमा के बर्फ में घुसावे न स। ए तकनीक के फ्रंट पोइंटिंग कहल जाला। बर्फ क ताकत अक्सर आश्चर्यजनक होला; कुल्हाड़ी खाली एको सेंटीमीटर बर्फ में घुस जा त ऊपर खींचे खातिर पर्याप्त होले। यदि पर्वतारोही कौनों अग्रणी ह, त उन्हन के अपनी सुरक्षा खातिर चढ़ाई की रास्ता में आइस स्क्रू क इस्तेमाल करे के पड़ेला।<ref name="ropesystemsonice">{{cite web | url = http://www.climbingextreme.com/2012/01/ice-climbing-ropes-and-single-vs-half/933 | title = Climbing Extreme: Ice climbing, ropes and single vs. half | author = climbingextreme.com, Juho Risku | date = January 2012 | accessdate = 2012-01-21 | archive-date = 2012-05-19 | archive-url = https://web.archive.org/web/20120519032556/http://www.climbingextreme.com/2012/01/ice-climbing-ropes-and-single-vs-half/933 | url-status = dead }}</ref> रॉक क्लाइम्बिंग क कुछ महत्वपूर्ण तकनीक और अभ्यास आइस क्लाइम्बिंग में सामान्य तौर पर हमेशा शामिल कइल जाला जेमें शामिल बा, रोप सिस्टम, टाइंग इन (tying in), बेलायिंग (belaying), लीडिंग (leading), अब्सेइलिंग (abseiling) और लोवेरिंग (lowering)। नौसिखआ लोगन के बर्फ पर चढ़ले की कोशिश कइले से पहिले ए तकनीकन के सीखे के चाहीं।<ref name="freedom">{{Cite book|title=Mountaineering: The Freedom of the Hills|publisher=The Mountaineers Books|location=Seattle |pages= |origyear=1960 |edition=7th |isbn=978-0898868289 |editor=Steven M. Cox and Kris Fulsaas |year=2003 |quote= |ref= }}</ref> ===रोप सिस्टम=== सिंगल, डबल और ट्विन रॉप बर्फ की चढ़ाई में इस्तेमाल होखे वाली तीनगो मुख्य रस्सी सिस्टम ह। सिंगल रोप सिस्टम, जवन सीधा चढ़ाई वाला मार्गन खातिर अनुकूल बा, इ दुनिया में सबसे अधिक इस्तेमाल कइल जाए वाला रॉक क्लाइम्बिंग सिस्टम ह। चढ़ाई में भी अक्सर डबल रॉप सिस्टम क इस्तेमाल भी कइल जाला, डबल रॉप सिस्टम, सिंगल रॉप सिस्टम की तुलना में अधिक लचीला सिस्टम होला। आखिर में, ट्विन रॉप सिस्टम, जवन की सिंगल रोप सिस्टम में दुगो जुड़वां रसरी क उपयोग करेला, ए सिस्टम क उपयोग लम्बा मल्टी-पिच रूट खातिर कइल जाला। ===टाइंग इन=== ''टाइंग इन'' चढ़े वाला उपकरण में रसरी फंसवले पर विशेष जोर देला। इ तकनीक चढ़ाई की नेतृत्व चाहें या बेलायिंग की दौरान बहुत आवश्यक होला। [[File:Canadian Rockies - Oh le tabernacle.jpg|156x240px|thumbnail|जमे हुए झरने पर कनाडाई रॉकी में चढ़ना]] ===लीडिंग=== ''लीडिंग'' क मतलब चढ़ाई की दौरान नेतृत्व से होला, एमें एगो लीडर और फोलोवर क जरुरत रहेला। ===अब्सेइलिंग=== एके रेप्पेल्लिंग भी कहल जाला, ए तकनीक में एगो बांधल रस्सी की सहारे नीचे उतरल जाला। ===लोवेरिंग=== इ नीचे उतरले क एगो बहुत ही सामान्य तरीका ह। ==संदर्भ== {{reflist}} [[श्रेणी:एडवेंचर]] jr0whcs03vpudcvkt7344818g4rt9hb एडवेंचर आइलैंड 0 48964 803291 768415 2026-07-29T16:06:11Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803291 wikitext text/x-wiki {{Notability}} इ क्षेत्र मेट्रो वॉक – कई तरह क अनुभव देवे वाला, '''एडवेंचर आइलैंड''' - मनोरंजन पार्क और लैगून - कृत्रिम झील की तौर पर विभाजित कइल गइल बा। ==एडवेंचर आइलैंड== एडवेंचर आइलैंड अंतरराष्ट्रीय स्तर पर प्रसिद्ध डिजाइन सलाहकार और आर्किटेक्ट फ़ॉरेक कनाडा, मैनेजमेंट सर्विसेज कैलिफ़ोर्निया, इलेक्ट्रासोनीक यूएसए, सिटी डिज़ाइन समूह अमरीका, ड्यू कॅनडा और टीयूवी जर्मनी द्वारा डिजाइन कइल गइल एगो विश्व स्तरीय मनोरंजन पार्क ह। इ 62 एकड़ में फैलल बा। इ पार्क विश्व स्तर प स्वीकार कइल गइल उच्चतम यूरोपीय सुरक्षा मानक डीआईएन-ड्यूश इंस्टिट्यूट फॉर नॉर्मंग क पालन करेला,जवन कि दुनिया भर में शीर्ष मनोरंजन पार्कन द्वारा पालन कइल जाला। रिठाला मेट्रो स्टेशन रोहिणी की सामने स्थित, एडवेंचर आइलैंड शहर की सब भागन से मेट्रो से और बाहरी एवं अंदरूनी रिंग रोड से जुड़ल बा।<ref>{{cite web|url=https://www.cleartrip.com/local/new%20delhi/adventure-island-amusement-park-in-new%20delhi-collection-4962-1|title=एडवेंचर आइलैंड मनोरंजन पार्क|publisher=cleartrip.com|access-date=2017-08-19|archive-date=2016-07-13|archive-url=https://web.archive.org/web/20160713074441/http://www.cleartrip.com/local/New%20Delhi/adventure-island-amusement-park-in-New%20Delhi-collection-4962-1|url-status=dead}}</ref> ==मेट्रो वॉक== मेट्रो वॉक पूरा देश में अपनी तरह क अलग और एकमात्र परियोजना ह।<ref>{{cite web|url=http://www.appughar.com/metro-walk/|title=मेट्रो वॉक|publisher=appughar.com|access-date=2017-08-19|archive-date=2019-03-05|archive-url=https://web.archive.org/web/20190305211007/http://www.appughar.com/metro-walk/|url-status=dead}}</ref> दिल्ली और एन सी आर की फैशन की प्रति रुझान रखे वाला समझदार ग्राहक लोगन खातिर इ एगो खरीदारी क जगह ह। एकरी द्वारा दीहल जाए वाला सुविधा की आधार प मेट्रो के अपनी शहर क केवल एक मात्र फैशन खातिर मशहूर होखले की जगह क गौरव प्राप्त भइल बा।<ref>{{cite web|url=http://archivefhw.financialexpress.com/20070615/market02.shtml|title=यूनिटेक, अंतर्राष्ट्रीय मनोरंजन मनोरंजन पार्क के लिए टाई|publisher=financialexpress.com}}{{Dead link|date=October 2023 |bot=InternetArchiveBot |fix-attempted=yes }}</ref> इ ओ ग्राहक लोगन के सेवा देला जे एगो आकर्षक माहौल में शानदार खरीदी का अनुभव प्राप्त कइल चाहेला। एमें एगो मनोरंजन पार्क और एगो पूरी तरह से सुसज्जित खुदरा वाणिज्यिक केंद्र भी शामिल बा जेमें देश की कईगो ब्रांड की साथ खईले पियले क जगह भी बा, ए प्रकार से इ आगंतुकन खातिर एगो पूर्ण मनोरंजन और खरीदारी क स्थल ह। ए सबकी साथ एइजा कई तरह क मनोरंजन क कार्यक्रम क आयोजन भी कइल जाला।<br> एइजा इंटरनेट कनेक्टिविटी क भी इन्तेजाम बा। इ नजदीकी मेट्रो स्टेशन से सिर्फ 5 मिनट की पैदल दूरी पर बा और सिर्फ 10 मिनट की ड्राइव पर एइजा से राष्ट्रीय राजमार्ग बा जवन, सोनीपत, पानीपत, अंबाला, चंडीगढ़ और अन्य उत्तरी बेल्ट्स की ओर जाला। ==सन्दर्भ== {{reflist}} 5ap3a52gmpbaz5b5b1unvzqjqix5w77 ऊटी 0 49407 803341 781268 2026-07-29T16:22:05Z SM7Bot 7808 /* बगइचा आ पार्क */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803341 wikitext text/x-wiki {{Infobox settlement | name = ऊटी | native_name = | native_name_lang = | other_name = ऊटकमंड, उदगमंडलम् | settlement_type = [[कस्बा]] आ [[हिल स्टेशन]] | image_skyline = Emerald Lake Nilgiris.jpg | image_alt = | image_caption = | nickname = हिल स्टेशन सभ के रानी | map_alt = | map_caption = | pushpin_map = India Tamil Nadu | pushpin_label_position = right | pushpin_map_alt = | pushpin_map_caption = | coordinates = {{coord|11.41|N|76.70|E|display=inline,title}} | subdivision_type = देस | subdivision_name = भारत | subdivision_type1 = राज्य | subdivision_type2 = जिला | subdivision_name1 = [[तमिल नाडु]] | subdivision_name2 = [[नीलगिरी जिला]] | established_title = | established_date = | founder = | named_for = | unit_pref = | area_footnotes = | area_total_km2 = | area_rank = | elevation_footnotes = <ref name="municipality">{{cite web|url=http://municipality.tn.gov.in/ooty/abt-muni.htm |title=About Municipality |access-date=15 February 2008 |publisher=municipality.tn.gov.in |url-status=dead |archiveurl=https://web.archive.org/web/20080115070843/http://municipality.tn.gov.in/ooty/abt-muni.htm |archivedate=15 January 2008 |df= }}</ref> | elevation_m = 2240 | population_total = 88,430 | population_as_of = 2011 | population_footnotes = <ref>{{Cite web|url=http://www.censusindia.gov.in/pca/SearchDetails.aspx?Id=688772|title=Census 2011|publisher=GOI}}</ref> | population_density_km2 = auto | population_rank = | population_demonym = | demographics_type1 = भाषा | demographics1_title1 = ऑफिशियल | timezone1 = [[भारतीय मानक समय|IST]] | utc_offset1 = +5:30 | postal_code_type = [[पिनकोड]] | postal_code = 643 001 | area_code = 91423 | area_code_type = टेलीफोन | registration_plate = TN 43 | website = }} '''उदगमंडलम्''' चाहे '''ऊटी''' ({{Lang|en|Ooty}}) [[तमिलनाडु]] में एगो [[कस्बा]] आ म्युनिस्पैलिटी बा आ ई एगो परसिद्ध [[हिल स्टेशन|हिल इस्टेशन]] ह। ई [[नीलगिरी जिला|नीलगिरि जिला]] के मुख्यालय भी बा। [[नीलगिरि|नीलगिरी]] के पहाड़ी पर {{convert|2240|m}} के ऊँचाई पर मौजूद ई जगह एगो परसिद्ध पहाड़ी पर्यटन स्थल बा। कोयंबटूर से 86 किमी उत्तर आ मैसूर से 158 किमी दक्खिन में मौजूद ई कस्बा नीलगिरि घाट सड़क आ नीलगिरि माउंटेन रेलवे से जुड़ल बाटे आ गर्मी के सीजन खातिर एगो पापुलर पर्यटन केंद्र हवे। 2011 में एह कस्बा के कुल आबादी 88,430 रहल। मूल रूप से ई एरिया टोडा लोग के रहल जे अठारहवीं सदी के अंत के समय में ईस्ट इंडिया कंपनी के कब्जा में आइल। एहिजे के अर्थब्यवस्था पर्यटन आ खेती-किसानी पर टिकल बाटे, एकरे साथे-साथ कुछ किसिम के दवाई आ फोटोग्राफिक फिल्म (रील) के मैनुफैक्चरिंग एहिजे होखे ला; कई ठो फिलिम सभ के शूटिंग के लोकेशन के रूप में भी एह जगह के बीछल जा चुकल बा। एकरा के पॉपुलर नाँव "क्वीन ऑफ़ हिल स्टेशंस" (हिल स्टेशन सभ के रानी) आ "क्वीन ऑफ़ वेस्टर्न घाट्स" (पच्छिमी घाट के रानी) से बोलावल जाला। ई मद्रास प्रेसिडेंसी के गर्मी के सीजन के राजधानियो रहल। == नाँव == एकरे नाँव के बारे में सटीक जानकारी नइखे। नाँव के पहिला लिखित परमान मार्च 1821 में ''मद्रास गैजेट'' नाँव के अखबार के लिखल एगो चिट्ठी में ''Wotokymund'' हिज्जे के साथे मिले ला। मानल जाला कि पुराना समय में ई ''ओट्टकल मंडू'' कहल जाय। इहो अनुमान लगावल जाला कि नाँव उदगमंडलम रहे जे बाद में, ब्रिटिश काल में, बदल के ऊटकमंड भ गइल, जे अउरियो बाद में छोट हो के ऊटी भइल। नाँव के पहिला हिस्सा संभव बा कि नीलगिरी परबत के बिचला हिस्सा के लोकल नाँव के बिगड़े से बनल हवे। ऊट्टा-कल के शाब्दिक अरथ "एक ठो पाथर" होखे ला। हो सके ला कि ई कौनों अइसन पबित्र पाथर रहल होखे जे लोकल टोडा लोग द्वारा मानल जात होखे। दुसरा हिस्सा मंड टोडा भाषा के मांडू शब्द के बिगड़ल रूप हवे जेकर अरथ गाँव होला। ऊटी नीलगिरि के परबत में बा; खुद नीलगिरि (नीला परबत) कुरुंजी नाँव के एगो फूल से आवे ला जे एह परबत पर लमहर समय (बारह बरिस) के बाद फुलाला आ पूरा ढाल सभ के नीलछाहूँ क देला। == इतिहास == उदगमंडलम् मूल रूप से आदिवासी लोगन के इलाका रहल आ एहिजे तमिल, टोडा, कोटा, ईरुला, आ कुरुंबा जनजाति के लोग रहे। 1818 में असिस्टेंट जे. सी. विश आ एन. डब्लू. किंडर्सले लोग जे जॉन सुलिवान, ओह समय के कोयंबटूर के जिला कलेक्टर, के सहजोगी रहल, ऊटी के जात्रा कइलें लोग आ रिपोट जमा कइल लोग। सुलिवान कोटागिरी के उत्तर में दिम्बाती में जनवरी 1819 में कैंप कइलें आ एकरे सुन्दरता के चकित रहि गइलें। मई 1819 में टोडा लोग एह क़स्बा के कुछ हिस्सा सुलिवान के दे दिहल आ ऊ दिम्भाती में आपन बंगला बनवावे शुरू कइलें। एही साल ऊ सिरुमुगाई से दिम्भाती खातिर रोड के निर्माण सुरू करववलें रो मई 1823 में बन के तइयार भइल आ बाद में 1830-32 में कुन्नूर ले बढावल गइल। अरानमोर महल जे ऊटी में बा, मद्रास प्रेसिडेंसी के राजधानी के रूप में काम करे गर्मी के सीजन खाती।ई ब्रिटिश अधिकारी लोग एहिजे आवे जब गर्मी के सीजन होखे आ ई पापुलर रिजार्ट बन गइल। सिपाही लोगन के तरोताजा होखे खातिर वेलिंगटन भेजल जाय। अब्बो वेलिंगटन भारतीय सेना के मदरास रेजिमेंट के घरेलू अस्थान हवे। भारत के आजादी के बाद ऊटी के हिल स्टेशन के रूप में बिकसित कइल गइल। == भूगोल == === लोकेशन === ऊटी [[नीलगिरि बायोस्फीयर रिजर्व]] में पड़े ला। कई सारा जंगली एरिया आ जलभंडार सभ तक आम पहुँच ना बाटे जे एह इलाका के नाजुक इकोसिस्टम के संरक्षित रखे खातिर सुरक्षित कइल गइल बाड़ें। बायोस्फीयर रिजर्व के कुल इलाका के कुछ हिस्सा चिन्हित कइल गइल बाड़ें जहाँ पर्यटन खातिर आवाजाही हो सके ला। साथे साथ अइसनो उपाय कइल गइल बाड़ें कि पर्यटक लोगन खातिर खुला रखे के संघे एह इलाका के सुरक्षो कइल जा सके।<ref>{{cite news|title= Ban on carry bags|url = http://www.hindu.com/2005/02/04/stories/2005020401370300.htm|archive-url = https://web.archive.org/web/20050220103729/http://www.hindu.com/2005/02/04/stories/2005020401370300.htm|url-status = dead|archive-date = 20 February 2005| location=Chennai, India|work= [[The Hindu]]|date=4 February 2005}}</ref> ऊटी [[नीलगिरि]] के पहाड़ी पर [[समुंद्र तल]] से {{convert|2240|m|ft|abbr=off}} के ऊँचाई पर बसल कस्बा हवे।<ref name="municipality"/> === जलवायु === [[File:Boat House in Ooty Lake, Ooty - Tamil Nadu.JPG|thumb|upright=1.2|ऊटी झील में एगो बोटहाउस]] ऊटी के जलवायु [[उपोष्णकटिबंधी हाइलैंड जलवायु]] वाला इलाका हवे जे [[कोपेन के जलवायु वर्गीकरण|कोपेन के जलवायु बर्गीकरण]] में Cwb प्रकार में आवे ला।<ref name="Climate-Data.org">{{cite web|title=Climate: Ooty – Climate graph, Temperature graph, Climate table|url=http://en.climate-data.org/location/24046/|publisher=Climate-Data.org|access-date=30 September 2015 | archive-url=https://web.archive.org/web/20140227100008/http://en.climate-data.org/location/24046/ |archive-date=27 February 2014}}</ref> भले ई [[उष्णकटिबंध]] के क्षेत्र में होखे, एकरे बावजूद इहाँ के मौसम साल भर सुहावन आ [[बसंत]] नियर रहे ला। हालाँकि, जनवरी-फरवरी में रात के तापमान ठंढा होखे ला। सालों भर औसत तापमान लगभग बरोबर रहे ला; सालाना औसत हाई टेम्परेचर {{convert|17|-|20|C|F|0}} आ औसत लो टेम्परेचर लगभग {{convert|5|-|12|C|F|0}} के बीचा में रहे ला। अबतक ले के सभसे बेसी रिकार्ड तापमान ऊटी में {{convert|28.5|C|F}} रहल बा जे एह उष्णकटिबंधी क्षेत्र में पड़े वाला जगह खातिर कम्मे बाटे आ [[दक्खिन एशिया|दक्खिनी एशिया]] के हिसाब से एकरा के ठंढा मानल जा सके ला। [[बरसात]] के सीजन एहिजा ठंढा आ तेज बयार भरल रहे ला। हवा के चलते ठंढा (विंड चिल प्रभाव) दिन में {{convert|5|C|F}} के एहसास करावे वाला हो सके ला। हवा एहिजा सालों भर तेज बहे ले। इहाँ रिकार्ड कइल गइल सभसे कम तापमान {{convert|-2|C|F}} रहल बा।<ref>{{cite web |url=http://citypatriots.com/asia/india/tamil-nadu/ooty |title=Ooty in Tamil Nadu |url-status=dead |archive-url=https://web.archive.org/web/20160305184727/http://citypatriots.com/asia/india/tamil-nadu/ooty |archive-date=5 March 2016}}</ref> एहिजे सालाना औसत बरखा {{convert|1050|mm|in}} होखे ला आ दिसंबर से मार्च के मौसम सूखल रहे ला। {{Weather box | location = Ooty (Udhagamandalam) 1981–2010, extremes 1901–2012 | width = auto | metric first = Yes | single line = Yes | Jan record high C = 26.8 | Feb record high C = 27.3 | Mar record high C = 27.5 | Apr record high C = 28.5 | May record high C = 28.0 | Jun record high C = 26.1 | Jul record high C = 22.8 | Aug record high C = 23.0 | Sep record high C = 23.3 | Oct record high C = 25.5 | Nov record high C = 25.4 | Dec record high C = 27.4 | year record high C = 28.5 | Jan high C = 21.4 | Feb high C = 22.1 | Mar high C = 23.2 | Apr high C = 23.3 | May high C = 22.9 | Jun high C = 18.7 | Jul high C = 17.7 | Aug high C = 17.7 | Sep high C = 19.2 | Oct high C = 19.3 | Nov high C = 19.5 | Dec high C = 20.9 | year high C = 20.5 | Jan low C = 6.2 | Feb low C = 7.1 | Mar low C = 9.3 | Apr low C = 11.2 | May low C = 11.8 | Jun low C = 11.4 | Jul low C = 11.1 | Aug low C = 11.2 | Sep low C = 10.7 | Oct low C = 10.4 | Nov low C = 9.0 | Dec low C = 7.1 | year low C = 9.7 | Jan record low C = -2.1 | Feb record low C = 0.0 | Mar record low C = 1.1 | Apr record low C = 5.0 | May record low C = 4.4 | Jun record low C = 2.2 | Jul record low C = 2.5 | Aug record low C = 4.6 | Sep record low C = 4.4 | Oct record low C = 0.0 | Nov record low C = -1.1 | Dec record low C = -1.1 | year record low C = -2.1 | rain colour = green | Jan rain mm = 9.7 | Feb rain mm = 7.4 | Mar rain mm = 20.6 | Apr rain mm = 73.1 | May rain mm = 98.7 | Jun rain mm = 124.1 | Jul rain mm = 149.4 | Aug rain mm = 97.9 | Sep rain mm = 132.1 | Oct rain mm = 169.6 | Nov rain mm = 110.4 | Dec rain mm = 47.7 | year rain mm = 1040.9 | Jan rain days = 1.1 | Feb rain days = 0.6 | Mar rain days = 1.9 | Apr rain days = 5.6 | May rain days = 8.7 | Jun rain days = 10.7 | Jul rain days = 11.2 | Aug rain days = 10.0 | Sep rain days = 10.1 | Oct rain days = 11.7 | Nov rain days = 7.6 | Dec rain days = 3.3 | year rain days = 82.4 |time day = 17:30 [[भारतीय मानक समय|आइएसटी]] | Jan humidity = 65 | Feb humidity = 62 | Mar humidity = 59 | Apr humidity = 66 | May humidity = 73 | Jun humidity = 84 | Jul humidity = 86 | Aug humidity = 87 | Sep humidity = 84 | Oct humidity = 84 | Nov humidity = 82 | Dec humidity = 72 |year humidity = 75 |source 1 = [[India Meteorological Department]]<ref name=IMDnormals> {{cite web | archive-url = https://web.archive.org/web/20200205040301/http://imdpune.gov.in/library/public/1981-2010%20CLIM%20NORMALS%20%28STATWISE%29.pdf | archive-date = 5 February 2020 | url = https://imdpune.gov.in/library/public/1981-2010%20CLIM%20NORMALS%20%28STATWISE%29.pdf | title = Station: Octacamund Climatological Table 1981–2010 | work = Climatological Normals 1981–2010 | publisher = India Meteorological Department | date = January 2015 | pages = 573–574 | access-date = 1 March 2020}}</ref><ref name=IMDextremes> {{cite web | archive-url = https://web.archive.org/web/20200205042509/http://imdpune.gov.in/library/public/EXTREMES%20OF%20TEMPERATURE%20and%20RAINFALL%20upto%202012.pdf | archive-date = 5 February 2020 | url = https://imdpune.gov.in/library/public/EXTREMES%20OF%20TEMPERATURE%20and%20RAINFALL%20upto%202012.pdf | title = Extremes of Temperature & Rainfall for Indian Stations (Up to 2012) | publisher = India Meteorological Department | date = December 2016 | page = M207 | access-date = 1 March 2020}}</ref> }} {{clear}} ==देखे-घूमे लायक जगह== ऊटी एगो लोकप्रिय पर्यटन स्थल हवे जहाँ शहर आ आसपास के कई जगह बाड़ी सऽ। ===बगइचा आ पार्क=== सरकारी गुलाब के बगीचा (पहिले सेंटेनरी गुलाब पार्क) भारत के सभसे बड़ गुलाब के बगीचा हवे। ई ऊटी शहर के विजयनगरम में एल्क हिल के ढलान पर स्थित बा। 2,200 मीटर (7,200 फीट) के ऊंचाई पर बा। आज ई बगीचा देश के सभसे बड़ गुलाब के संग्रह में से एक बाटे जहाँ 2,800 कल्टीवेर सभ के 20,000 से ढेर किसिम के गुलाब के संग्रह बाटे। एह संग्रह में हाइब्रिड टी गुलाब, मिनीएचर रोज़, पॉलियान्थास, पपाजेना, फ्लोरिबुंडा, रैम्बलर्स, याकिमोर आ करिया आ हरियर जइसन असामान्य रंग के गुलाब शामिल बा. 8.9 हेक्टेयर (22 एकड़) के ऊटी वनस्पति उद्यान के बिस्तार 1847 में भइल आ एकर रखरखाव तमिलनाडु सरकार द्वारा कइल जाला। वनस्पति उद्यान रसीला, हरियर, आ बढ़िया से रखरखाव कइल गइल बा। हर मई में दुर्लभ पौधा प्रजाति के प्रदर्शनी के संगे फूल शो के आयोजन कइल जाला। एह बगीचा सभ में लगभग एक हजार प्रजाति के पौधा, झाड़ी, फर्न, पेड़, हर्बल आ बोनसाई पौधा सभ के बिदेसी आ देशी दुनों प्रजाति पावल जालीं। एह बगीचा में 20 मिलियन साल पुरान जीवाश्म वाला पेड़ बा। हिरण पार्क ऊटी झील के किनारे स्थित बा। उत्तराखंड के नैनीताल के चिड़ियाघर के छोड़ के ई भारत के सभसे ऊँच ऊँचाई वाला चिड़ियाघर हवे। ई पार्क कई प्रजाति सभ के हिरण आ अउरी जानवर सभ के रखे खातिर बनावल गइल == संदर्भ == {{Reflist|35em}} ==अउरी पढ़े खातिर== * {{cite book |title=Decaying splendours: two palaces: reflections in an Indian mirror |last=Weeks |first=Stephen |year=1979 |publisher=British Broadcasting Corporation |location=University of California |isbn=978-0-563-17516-2 |url=https://books.google.com/books?id=gTo2AAAAIAAJ&q=Decaying+Splendours;+two+palaces:+reflections+in+an+Indian+mirror|access-date=19 August 2011}} == बाहरी कड़ी == * [http://www.nilgiris.tn.gov.in/tour_ooty.html ऑफिशियल इतिहास आ पर्यटन वेबसाइट]{{Dead link|date=September 2023 |bot=InternetArchiveBot |fix-attempted=yes }} {{In lang|en}} {{Authority control}} [[श्रेणी:तमिल नाडु के शहर आ कस्बा]] [[श्रेणी:भारत के हिल स्टेशन]] 90j93a568ktg3w9osesu63peulgi9g8 दक्षिण अफ़्रीका एयरवेज 0 49440 803344 762661 2026-07-29T16:22:17Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803344 wikitext text/x-wiki {{Infobox airline | airline = South African Airways | logo = | IATA = SA | ICAO = SAA | callsign = SPRINGBOK | logo_size = 200 | founded = {{Start date and age|1934|02|01|df=y}} | focus_cities = {{nowrap|Cape Town International Airport}} | hubs = {{nowrap|O.R. Tambo International Airport}} | frequent_flyer = Voyager | alliance = Star Alliance | fleet_size = 53 | destinations = 42 | parent = Government of South Africa | company_slogan = ''Bringing the world to Africa and taking Africa to the world'' | headquarters = OR Tambo International Airport<br>Kempton Park, Gauteng, Ekurhuleni Metropolitan Municipality, Gauteng, South Africa | key_people = Musa Zwane (Acting CEO) | revenue = {{increase}} [[South African rand|R]]27,1&nbsp;billion (2012/13 <abbr title="Fiscal year">FY</abbr>)<ref name="defenceweb.co.za">{{cite web|url=http://www.defenceweb.co.za/index.php?option=com_content&view=article&id=33511:saa-revenue-up-but-losses-continue-for-2013&catid=114:civil-aviation&Itemid=247|title=SAA revenue up but losses continue for 2013}}</ref> | operating_income = {{decrease}}South African rand 60&nbsp;million (2012/13 <abbr title="Fiscal year">FY</abbr>) <ref>{{cite web|url=http://www.flysaa.com/bi/ja/Documents/Financials/SAA_AR_2012_web.pdf|title=Fiscal|publisher=flysaa.com}}</ref> | profit = {{decrease}} South African rand-991&nbsp;million Loss (2012/13 <abbr title="Fiscal year">FY</abbr>)<ref name="defenceweb.co.za"/> | assets ={{decrease}} [[South African rand|R]]14,044&nbsp;million (2009/10 <abbr title="Fiscal year">FY</abbr>)<ref name=SAA_2010_Annual_Report>{{cite web|title=Sustain Profitability: Annual Report 2010|publisher=South African Airways|url=https://www.flysaa.com/pv_obj_cache/pv_obj_id_5D76D3D217737135BBB491607A891BB54A324000/filename/SAAAnnualReport2010.pdf|accessdate=31 December 2010}}</ref>{{rp|37}} | subsidiaries =Mango |num_employees = 8,525 (2016)<ref>{{cite web|url=http://www.staralliance.com/en/member-airline-details?airlineCode=SA|accessdate=29 March 2017|title=South African Airways|date=नवंबर 2016}}</ref> | website ={{URL|https://www.flysaa.com}} }} दक्षिण अफ़्रीकी एयरवेज (एसएए) दक्षिण अफ्रीका का ध्वजवाहक ह। एकर मुख्यालय ऑर तामो अंतरराष्ट्रीय हवाई अड्डा की एयरवेज पार्क प बा, जवन की केम्पटन पार्क, एकूरहुलेनी, गौतेंग में बा। इ एयरलाइन, एसए एक्सप्रेस, एसए एयरलाइन और एकर कम लागत वाली वाहक, मैंगो की साथ साझेदारी में, दक्षिण अफ्रीका की अन्दर और पूरा महाद्वीप में, और एकरी जोहान्सबर्ग हब ऑर तामो अंतरराष्ट्रीय हवाई अड्डा से आठ अंतरमहाद्वीपीय मार्गन प एकरी 54 एयरक्राफ्ट की बेड़ा की साथ उड़ान भरेले। मूसा झवेन नवंबर 2015 से एकर कार्यकारी सीईओ हंवं।<ref>{{cite web| url=https://www.flysaa.com/about-us/leading-carrier/about-saa/brief-history| title= दक्षिण अफ़्रीकी एयरवेज के बारे में|publisher=flysaa.com}}</ref> दक्षिण अफ्रीकी सरकार द्वारा यूनियन एयरवेज क अधिग्रहण की बाद 1934 में दक्षिण अफ्रीकी एयरवेज क स्थापना भइल रहे। ए एयरलाइन क शुरूआत में दक्षिण अफ्रीकी रेलवे और हार्बर्स प्रशासन द्वारा देखरेख और नियंत्रण कइल गइल। अफ्रीकी देशन द्वारा रंगभेद विरोधी प्रतिबंधन से रंगभेद की दौरान ठहराव वाला हवाईअड्डन से एयरलाइन वंचित हो गइली स, जेसे उन्हन के ए महाद्वीप के लंबा दूरी की विमानन की साथ बाईपास करे के मजबूर होखे के पड़ल। ए समय की दौरान, एके एकरी अफ्रीकी नांव, सुद-अफ्रिकाएनस ल्यूगेंस (एसएएल) द्वारा भी जानल जाव, जेके बाद में हटा दिहल गइल। 1997 में एसएए आपन नांव, छवि और विमान क वेशभूषा बदल दिहलस और ऑनलाइन टिकट सेवा शुरू कइलस। 2006 में, एसएए एगो स्वतंत्र एयरलाइन की रूप में संचालित करे खातिर ट्रांसनेट, जवन की ओकर मूल कंपनी रहे ओसे अलग हो गइल। एसएए टेनिस पेशेवरन की एसोसिएशन क आधिकारिक एयरलाइन ह।  SAA, एगो कम लागत वाली घरेलू एयरलाइन मैंगो क मालिक ह, और ऐयरलिंक और दक्षिण अफ्रीकी एक्सप्रेस की साथ संबंध स्थापित कइले बा।इ स्टार अलायंस क सदस्य ह। ==इतिहास== [[File:Junkers-f13.jpg|thumb|एसएए ने कई संयुग्मित यूनियन एयरवेज के विमानों के साथ आपरेशन की शुरुआत की, जिसमें चित्रित चित्र जैसा जंकर्स एफ.13 भी शामिल है]] दक्षिण अफ्रीकी सरकार द्वारा यूनियन एयरवेज की अधिग्रहण की बाद 1 फरवरी 1934 के दक्षिण अफ्रीकी एयरवेज क गठन भइल। चालीस कर्मचारी सदस्य, एगो डे हवललैंड DH.60 जिप्सी मॉथ, एगो डे हैविलैंड 80ए पुस मॉथ, तीनगो जंकर एफ-13एस और एगो पट्टा पर लिहल जंकरर्स एफ 13 और जंकर्स ए50 अधिग्रहण में शामिल रह न स। अधिग्रहण की बाद, सरकार एयरलाइन क नांव बदल के दक्षिण अफ्रीकी एयरवेज क दिहलस और एकर नियंत्रण दक्षिण अफ्रीकी रेलवे और हार्बर प्रशासन (अब ट्रांसनेट) के दे दिहलस। चार्टर परिचालन क ओ ही वर्ष में शुरुआत हो गइल रहे। अगिला वर्ष में 1 फरवरी के, इ विमान कम्पनी स्वीड्स ल्यूगेंस / दक्षिण पश्चिम एयरवेज (अब एयर नामीबिया) के अधिग्रहण कइलस, जवन एकरी पहिले 1932 से विंडहोक और किमबर्ली की बीच साप्ताहिक एयर-मेल क सेवा प्रदान करे।<ref>{{cite web|url=https://www.flightglobal.com/pdfarchive/view/1966/1966%20-%201219.html|title= SOUTH AFRICAN AIRWAYS|publisher=Flight International}}</ref><ref>{{cite web| url=https://www.cleartrip.com/flight-booking/south-african-airlines.html| title=दक्षिण अफ़्रीकी एयरवेज| publisher=क्लेअरट्रिप. कॉम}}</ref> ए समय की दौरान, दक्षिण अफ्रीकी एयरलाइन तीनगो जंकर्स जू 52/3 एम विमान क ऑर्डर दिहलस, जवन की अक्टूबर 1934 में मिलल और 10 दिन बाद सेवा में उतारल गइल। ए विमानन के चार चालक दल की साथ 14 यात्री ले जाए खातिर कॉन्फ़िगर कइल गइल रहे। इन्हन से डरबन-ईस्ट लंदन-पोर्ट एलिजाबेथ-जॉर्ज / मोसेल बे-केप टाउन मार्ग पर साप्ताहिक सेवा की साथ तीन बेर साप्ताहिक डरबन-जोहान्सबर्ग सेवा सक्षम भइल।<ref>{{cite web|url=http://active.boeing.com/commercial/orders/displaystandardreport.cfm?cboCurrentModel=707&optReportType=AllModels&cboAllModel=707&ViewReportF=View+Report|title=707 Model Summary|publisher=Boeing Commercial Airplanes|access-date=2017-09-06|archive-date=2015-09-04|archive-url=https://web.archive.org/web/20150904102020/http://active.boeing.com/commercial/orders/displaystandardreport.cfm?cboCurrentModel=707&optReportType=AllModels&cboAllModel=707&ViewReportF=View+Report|url-status=dead}}</ref> 1 जुलाई 1935 के, SAA आपन संचालन रैंड हवाई अड्डा प स्थानांतरित क दिहलस काहेंकी इ तेजी से स्पष्ट हो गइल रहे की जोहान्सबर्ग देश का विमानन केन्द्र बन जाई, जवन की रैंड-डरबन-ईस्ट लंदन-पोर्ट एलिजाबेथ-केप टाउन सेवा की शुभारंभ की साथ भइल। ==सन्दर्भ== {{reflist}} [[श्रेणी:हवाई जहाज]] [[श्रेणी:हवाई जहाज सेवा]] j5w52xo4tla42fsqrfwog1euyotyicy पश्चिम एक्सप्रेस 0 49514 803330 762750 2026-07-29T16:21:22Z SM7Bot 7808 /* रेल इंजन */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803330 wikitext text/x-wiki {{Infobox rail service | name = पश्चिम एक्सप्रेस | image= 12925 Paschim Express.jpg | caption = | type = सुपरफास्ट एक्सप्रेस | states = महाराष्ट्र, गुजरात, मध्य प्रदेश, राजस्थान, उत्तर प्रदेश, हरियाणा, दिल्ली, पंजाब | first = 24 December 1956 | last = | operator = पच्छिमी रेलवे जोन | ridership = | start = बांद्र टर्मिनस | stops = 42 | end =अमृतसर जंक्शन | distance = {{convert|1821|km|0|abbr=on}} | journeytime = 31&nbsp;घंटा 10&nbsp;मिनट | frequency = रोज | class = एसी 1ली श्रेणी, एसी 2 टायर, एसी 3 टायर, स्लीपर दर्जा, जनरल दर्जा | seating = Yes | sleeping = Yes | catering = मौजूद, पैंट्री कार से | observation = | entertainment = | baggage = | other facilities= | stock = Standard Indian Railway coaches | gauge = {{RailGauge|1676mm|allk=on}} | el = | trainnumber = 12925 / 12926 | speed = {{convert|110|km/h|0|abbr=on}} अधिकतम <br/>{{convert|58.43|km/h|0|abbr=on}}, हाल्ट जोड़ के | map = [[File:Paschim Express (Amritsar - Mumbai) Route map.jpg|250px]] | map_state = }} '''12925 / 12926 पश्चिम एक्सप्रेस''' भारतीय रेलवे क एगो सुपरफास्ट एक्सप्रेस ट्रेन ह और इ पंजाब में बांद्रा टर्मिनस और अमृतसर की बीच में चलेले। एमें 22925/26 क स्लीप कोच होन स जवन कि कालका की ओर जान स। इ ट्रेन दैनिक सेवा देले, इ बांद्रा टर्मिनस से अमृतसर ले रेल संख्या 12925 और वापसी में ट्रेन नंबर 12926 की रूप में संचालित होले। ==कोच== 12925/26 पश्चिम एक्सप्रेस में 1गो एसी प्रथम श्रेणी, 3गो एसी 2 टीयर, 5गो एसी 3 टीयर, 8गो स्लीपर क्लास, 3गो जनरल अनारक्षित कोच और 4 जनरल तथा समान कोच होंन स। भारत की अधिकतर ट्रेन सेवा की तरह, मांग की अनुसार भारतीय रेलवे अपनी विवेक की अनुसार एकरी कोच संरचना में बदलाव क सकेला। ==सेवा== पश्चिम एक्सप्रेस मुंबई और अमृतसर, कालका की बीच चलेले। अम्बाला कैंट जंक्शन से ए ट्रेन क दुगो भाग हो जाला। इ ट्रेन दैनिक सेवा देले जवने की तहत 31 घंटा 45 मिनट में 1821 किलोमीटर क दूरी 12925 पश्चिम एक्सप्रेस की रूप में 57.35 किमी/घंटा की औसत रफ्तार से और 30 घंटा 35 मिनट में 12926 पश्चिम एक्सप्रेस की रूप में तय करेले और ए दौरान एकर औसत रफ़्तार 59.54 किमी/घंटा होला। कुल दूरी तय कइले क औसत गति (बांद्रा टर्मिनस - अमृतसर और वापसी क) 58.43 किमी/घंटा बा। ==रेल इंजन== पहिले  WCAM 1 या  WCAM 2/2P जैसन दुगो इंजन बांद्रा टर्मिनस और वडोदरा की बीच ए ट्रेन के चलावत रहन हं स और ओकरी बाद गाजियाबाद स्थित WAP 1 इंजन द्वारा शेष यात्रा पूरा कइल जात रहल ह। जब पश्चिम रेलवे 5 फरवरी 2012 के डीसी इलेक्ट्रिक कनवर्ज़न के एसी में बदलले क काम पूरा कइलस, तबसे गाजियाबाद की  WAP 7 चाहें WAP 5 इंजन द्वारा सम्पूर्ण यात्रा पूरा कइल जाला। ==ट्रेन क इतिहास== * पश्चिम एक्सप्रेस के अनौपचारिक रूप से पश्चिम सुपर डीलक्स एक्सप्रेस की रूप में जानल जाला। * इ मुंबई सेंट्रल से बांद्रा टर्मिनस स्थानांतरित होखे वाली पहिला कुछ गाड़ी में से एगो रहे। * जब इ 12926 पश्चिम एक्सप्रेस की रूप में संचालित होले तब इ हज़रत निजामुद्दीन स्टेशन प नाहीं रुकेले। {{clear}} ==गैलरी== <gallery> File:WAP 7 engine of the 12926 Paschim Express at Bandra Terminus.jpg|बांद्रा टर्मिनस में 12 9 26 पश्चिम एक्सप्रेस के वैप 7 इंजन File:AC 2 tier - 2925 Paschim Express.jpg|एसी 2 स्तरीय - 2 9 25 पश्चिम एक्स्प्रेस File:AC 1st Class - 2925 Paschim Express.jpg|एसी प्रथम श्रेणी - 2 9 25 पश्चिम एक्स्प्रेस File:12925_Paschim_Express_at_Borivali_railway_station.jpg|12 9 5 पश्चिम बोरिवली रेलवे स्टेशन पर पश्चिम एक्स्प्रेस File:2925 Paschim Express with a WAP 1 engine.JPG|एक डब्लूएपी 1 इंजन के साथ 2925 पश्चिम एक्सप्रेस File:22925 Paschim Express.jpg|22925 पश्चिम एक्सप्रेस File:22925 Paschim Express - AC 3 tier.jpg|22925 पश्चिम एक्सप्रेस - AC 3 टियर File:22925 Paschim Express at Borivali.jpg|22925 पश्चिम में एक्सप्रेस बोरिवली रेलवे स्टेशन File:12926 Paschim Express at Bandra Terminus.jpg|22926 पश्चिम में एक्सप्रेस बांद्रा टर्मिनस </gallery> ==टाइम टेबल== 12925 पश्चिम एक्सप्रेस बांद्रा टर्मिनस से हर दिन 12.00 बजे प्रस्थान करेले और अगिला दिन 19.20 बजे अमृतसर पहुंचेले। वापसी में, 12926 पश्चिम एक्सप्रेस अमृतसर से हर दिन 08:10 बजे प्रस्थान करेले और अगिला दिन 14.45 बजे बांद्रा टर्मिनस पहुंचेले।<ref>{{cite web| url=http://www.irfca.org/faq/faq-seltrain.html#other| title=प्रसिद्ध गाड़ियों| publisher=irfca.org}}</ref><ref>{{cite web| url=https://www.cleartrip.com/trains/12926| title=पश्चिम एक्सप्रेस| publisher=क्लेअरट्रिप. कॉम| access-date=2017-09-13| archive-date=2017-03-27| archive-url=https://web.archive.org/web/20170327171816/https://www.cleartrip.com/trains/12926| url-status=dead}}</ref> {| class="wikitable plainrowheaders unsortable" style="text-align:center" |- !scope="col" rowspan="2" style="background:gold;"|स्टेशन कोड !scope="col" rowspan="2" style="background:gold;"|स्टेशन का नाम !scope="col" colspan="2" style="background:gold;"|12925 - बांद्रा टर्मिनस से अमृतसर !scope="col" rowspan="2" style="background:gold;"|दूरी स्रोत से किमी में !scope="col" rowspan="2" style="background:gold;"|दिन !scope="col" colspan="2" style="background:gold;"|12926 - अमृतसर से बांद्रा टर्मिनस तक !scope="col" rowspan="2" style="background:gold;"|दूरी स्रोत से किमी में !scope="col" rowspan="2" style="background:gold;"|दिन |- !scope="col" colspan="1" style="background:gold;"|आगमन !scope="col" colspan="1" style="background:gold;"|प्रस्थान !scope="col" colspan="1" style="background:gold;"|आगमन !scope="col" colspan="1" style="background:gold;"|प्रस्थान |- | valign="center" |BDTS | valign="center" |बांद्रा टर्मिनस | valign="center" |स्रोत | valign="center" |12:00 | valign="center" |0 | valign="center" |1 | valign="center" |14:45 | valign="center" |गंतव्य | valign="center" |1821 | valign="center" |2 |- | valign="center" |BRC | valign="center" |वडोदरा जंक्शन | valign="center" |18:03 | valign="center" |18:13 | valign="center" |381 | valign="center" |1 | valign="center" |08:16 | valign="center" |08:26 | valign="center" |1440 | valign="center" |2 |- | valign="center" |RTM | valign="center" |रतलाम जंक्शन | valign="center" |22:20 | valign="center" |22:30 | valign="center" |642 | valign="center" |1 | valign="center" |04:00 | valign="center" |04:10 | valign="center" |1179 | valign="center" |2 |- | valign="center" |KOTA | valign="center" |कोटा जंक्शन | valign="center" |02:05 | valign="center" |02:15 | valign="center" |909 | valign="center" |2 | valign="center" |23:30 | valign="center" |23:40 | valign="center" |913 | valign="center" |1 |- | valign="center" |GGC | valign="center" |गंगापुर सिटी | valign="center" |04:40 | valign="center" |04:42 | valign="center" |1081 | valign="center" |2 | valign="center" |21:13 | valign="center" |21:15 | valign="center" |741 | valign="center" |1 |- | valign="center" |NDLS | valign="center" |नई दिल्ली | valign="center" |10:40 | valign="center" |11:05 | valign="center" |1373 | valign="center" |2 | valign="center" |16:25 | valign="center" |16:45 | valign="center" |448 | valign="center" |1 |- | valign="center" |UMB | valign="center" |अम्बाला कैंट जंक्शन | valign="center" |14:40 | valign="center" |14:55 | valign="center" |1572 | valign="center" |2 | valign="center" |12:35 | valign="center" |13:05 | valign="center" |250 | valign="center" |1 |- | valign="center" |ASR | valign="center" |अमृतसर | valign="center" |19:20 | valign="center" |गंतव्य | valign="center" |1821 | valign="center" |2 | valign="center" |स्रोत | valign="center" |08:10 | valign="center" |0 | valign="center" |1 |} ==संदर्भ== {{reflist}} [[श्रेणी:भारतीय रेल के गाड़ी]] [[श्रेणी:भारतीय रेल]] 6c6llfmfrsqoh1rn26u6r6z37wlh0hm पितरपख 0 49553 803384 697990 2026-07-29T17:49:35Z SM7 3953 +विकिकड़ी जोड़ल गइल 803384 wikitext text/x-wiki {{Infobox holiday |holiday_name = पितरपख |type = हिंदू |image = Pitru Paksha 2007.jpg |caption = बाणगंगा पोखरा के किनारे पितरपख के नहान आ जल दिहल, 7 सितंबर 2007 |observedby = [[हिंदू]] |begins = भादो के पुर्नवासी |ends = कुआर के अमौसा |date = सितंबर/अक्टूबर |date2016 = सितंबर 16 – अक्टूबर 1 <ref>{{cite web|url=http://www.drikpanchang.com/vrats/shraddhadates.html|title=2016 Shraddha Days|publisher=दृक पञ्चांग}}</ref> |date2017 = सितंबर 5-19 |celebrations = 16 [[तिथी|चंद्रमा तिथी]] |observances = श्राद्ध |relatedto = पितर (पूर्वज) लोग के तर्पण }} '''पितरपख''' (संस्कृत: पितृपक्ष) [[कुआर]] महीना के [[अन्हार पाख|अन्हरिया वाला पाख]] के कहल जाला जे [[भादो]] के [[पुर्नवासी]] के अगिला दिन से सुरू हो के कुआर के [[अमौसा]] ले, कुल सोरह दिन, रहे ला आ एह समय के दौरान [[हिंदू|हिंदू लोग]] अपना पितर लोग के तिथी मनावे ला आ सालाना श्राद्ध करे ला। एकरा के '''पितृपक्ष''', आ '''सोरह श्राद्ध''' के नाँव से भी जानल जाला। [[File:Tarpan - Jagannath Ghat - Kolkata 2012-10-15 0648.JPG|thumb|जगन्नाथ घाट हुगली नदी के किनारे पितरपख के नहान, कोलकाता 2012-10-15]] एह अवधि के हिंदू लोग अशुभ माने ला, काहें से की एह में मौत आ मर चुकल लोग के पूजा (श्राद्ध) के बिधान हवे। एही कारण कई तरह के शुभ कामकाज एह दौरान बंद रहे ला। एकरे अंतिम तिथी के भोजपुरी क्षेत्र में '''पितरसउनन''' कहल जाला, जबकि अन्य क्षेत्रन में सर्वपित्री अमावस्या, पितृ अमावस्या, पेद्दल अमावस, महालय अमावस्या भा महालय कहल जाला। महालय से बंगाल में [[दुर्गा पूजा]] के सुरुआत मानल जाला। अगिला दिन से [[नवरातर]] के सुरुआत हो जाला। {{clear}} ==संदर्भ== {{Reflist|35em}} {{हिंदू तिहुआर}} [[श्रेणी:हिंदू तिहुआर]] [[श्रेणी:कुआर महीना के तिहुआर]] {{hinduism-stub}} 4p490wrd4w6omxpw64t21nfl9d506yq गौतम अडानी 0 50477 803364 801917 2026-07-29T16:23:53Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803364 wikitext text/x-wiki {{Infobox person | name = गौतम अडानी | image = Gautam Adani.jpg | alt = | caption = | birth_name = गौतम शांतिलाल अडानी | birth_date = {{Birth date and age|df=yes|1962|06|24}} | birth_place = [[अहमदाबाद]], गुजरात, भारत | residence = अहमदाबाद, गुजरात, भारत | death_date = <!-- {{Death date and age|df=yes|YYYY|MM|DD|YYYY|MM|DD}} or {{Death-date and age|Month DD, YYYY|Month DD, YYYY}} (death date then birth date) --> | death_place = | nationality = [[भारत के लोग|भारतीय]] | other_names = |networth = $8.81 बिलियन (सितंबर 2017)<ref name="Forbes">{{cite web|title=Gautam Adani & family|url=https://www.forbes.com/profile/gautam-adani/|website=Forbes|accessdate=6 अप्रैल 2017}}</ref> | known_for = संस्थापक आ चेयरमैन, अडानी ग्रुप | occupation = बिजनेसमैन | parents = शांतिलाल अडानी<br/>शांता अडानी | spouse = प्रीती अडानी | children = करन<br/>जीत | relatives = बिनोद अडानी (भाई) }} '''गौतम शांतिलाल अडानी''' (जनम 24 जून 1962) एगो भारतीय अरबपति उद्योगपति आ फिलेंथ्रोपिस्ट (परोपकारी, मानवताप्रेमी) बाड़ें।<ref>{{Cite news|title=Gautam Adani, Nita Ambani & KM Birla top philanthropic leaders in India|work=The Economic Times|url=https://economictimes.indiatimes.com/magazines/panache/gautam-adani-nita-ambani-km-birla-top-philanthropic-leaders-in-india/articleshow/85291662.cms|access-date=2022-01-15}}</ref> ऊ भारत में बंदरगाह बिकास आ संचालन से जुड़ल अहमदाबाद के बहुराष्ट्रीय समूह [[अडानी ग्रुप]] के [[अध्यक्ष]] आऊर संस्थापक बाड़ें।<ref name="Bloomberg">{{cite news|title= Gautam Adani Bloomberg Index |newspaper= Bloomberg.com |url= https://www.bloomberg.com/billionaires/profiles/gautam-s-adani/ |access-date=11 February 2022}}</ref> अडानी [[अडानी फाउंडेशन]] के अध्यक्ष भी बाड़े, जवना के नेतृत्व मुख्य रूप से उनुकर पत्नी [[प्रीति अडानी]] करेली। ==सुरुआती जिनगी== अडानी के जनम 24 जून 1962 के [[गुजरात]] के बनिया परिवार में शांतिलाल आ शांति अडानी के घरे गुजरात के [[अहमदाबाद]] में भइल।<ref>{{cite news|url=http://www.businessworld.in/amp/article/The-Rise-Of-The-Tycoon/05-02-2018-139609/|title=The Rise Of The Tycoon: Gautam Adani|website=[[Businessworld]]|first=Himani|last=Chandna|date=2019-02-26|access-date=2022-06-24|archive-date=2022-03-17|archive-url=https://web.archive.org/web/20220317122500/https://www.businessworld.in/amp/article/The-Rise-Of-The-Tycoon/05-02-2018-139609/|url-status=dead}}</ref> इनके 7 गो भाई-बहिन बाड़ें आ इनके माता-पिता गुजरात के उत्तरी हिस्सा के थराद कस्बा से पलायन कइले रहलें।<ref>{{cite news |title=A look into profile of billionaire Gautam Adani |url=https://www.indiatoday.in/magazine/profile/story/20110829-profile-of-billionaire-gautam-adani-747307-2011-08-20 |access-date=23 May 2022 |work=India Today |date=20 August 2011 |language=en}}</ref> इनके पिता एगो छोट कपड़ा के व्यापारी रहलें।<ref>{{cite news |url=https://www.indiatvnews.com/business/india/top-10-gujarati-billionaires-3732.html?page=4 |title= Top 10 Gujrati Billionaires |website= [[India TV]] |date= 2015-08-01}}</ref> इनके पढ़ाई अहमदाबाद के शेठ चिमनलाल नागींदास विद्यालय में भइल। गुजरात विश्वविद्यालय में वाणिज्य में स्नातक के डिग्री खातिर नामांकन लिहलें, बाकी दुसरा साल के बाद पढ़ाई छोड़ दिहलें।<ref name="gujarati">{{cite news |last1=Joseph |first1=Anto T. |title=Gautam Adani: Another Gujarati who made it big |url=https://economictimes.indiatimes.com/news/company/corporate-trends/gautam-adani-another-gujarati-who-made-it-big/articleshow/2605913.cms |access-date=23 May 2022 |work=The Economic Times |date=8 December 2007}}</ref> अडानी के बिजनेस में इंटरेस्ट रहे, लेकिन बाबूजी के कपड़ा के बिजनेस में ना।<ref>{{cite journal |url= https://www.cambridge.org/core/journals/business-history-review/article/transnational-indian-business-in-the-twentieth-century/6B6236551634D106A0E65880574D7373/core-reader# |title= Transnational Indian Business in the Twentieth Century |author= Cambridge Core |journal= Business History Review |date= 2017-12-12|volume= 91 |issue= 4 |pages= 651–679 |doi= 10.1017/S0007680517001350 |s2cid= 158483062 }}</ref> ==कैरियर== [[File:Secretary Kerry greets Executive Chairman Adani before meeting with Indian CEOs amid Vibrant Gujarat Summit.jpg|right|thumb|अमेरिका के सेक्रेटरी ऑफ़ स्टेट जॉन कैरी अडानी के स्वागत करत, 2014]] किशोरावस्था में अडानी महेंद्र ब्रदर्स खातिर हीरा सॉर्टर के काम करे खातिर 1978 में [[मुंबई]] आ गइलें।<ref>{{cite journal|url=https://www.cambridge.org/core/journals/business-history-review/article/transnational-indian-business-in-the-twentieth-century/6B6236551634D106A0E65880574D7373/core-reader#|title= Transnational Indian Business in the Twentieth Century|date=2017-12-12|journal= Business History Review|volume=91|issue=4|pages=651–679|doi=10.1017/S0007680517001350|last1=Tumbe|first1=Chinmay|s2cid=158483062}} </ref> 1981 में उनुकर बड़ भाई महासुखभाई अडानी अहमदाबाद में एगो प्लास्टिक यूनिट खरीदले अउरी उनुका के संचालन के प्रबंधन करे के नेवता देले। ई उद्यम पॉलीविनाइल क्लोराइड (पीवीसी) आयात के माध्यम से वैश्विक व्यापार के अडानी के प्रवेश द्वार निकलस।<ref name="gujarati"/> 1985 में ऊ छोट पैमाना पर उद्योगन खातिर प्राथमिक बहुलक के आयात शुरू कइलें 1988 में अडानी अडानी समूह के होल्डिंग कंपनी अडानी एक्सपोर्ट्स के स्थापना कइलें, जेकरा के अब अडानी इंटरप्राइजेज के नाँव से जानल जाला। मूल रूप से ई कंपनी कृषि आ बिजली के चीज सभ के कारोबार करत रहे।<ref name="gujarati"/> 1991 में आर्थिक उदारीकरण के नीति सभ उनके कंपनी खातिर अनुकूल निकलल आ ऊ बिजनेस सभ के बिस्तार धातु, कपड़ा आ कृषि उत्पाद सभ के बिजनेस में करे लगलें।<ref name="gujarati"/><ref>{{cite news |last1=Vora |first1=Rutam |title=Gautam Adani: Cementing his portfolio |url=https://www.thehindubusinessline.com/specials/corporate-file/gautam-adani-cementing-his-portfolio/article65439045.ece |access-date=23 May 2022 |work=www.thehindubusinessline.com |date=22 May 2022 |language=en}}</ref> 1994 में गुजरात सरकार मुंद्रा बंदरगाह के प्रबंधकीय आउटसोर्सिंग के घोषणा कइलस आ 1995 में अडानी के ई ठेका मिल गइल।<ref>{{cite news|url=https://timesofindia.indiatimes.com/news/Gautam-Adani-the-baron-to-watch-out-for-if-Narendra-Modi-becomes-king/articleshow/33521536.cms|title=Gautam Adani, the baron to watch out for if Narendra Modi becomes king|date=2014-04-10|work=[[The Times of India]]|first=Harit|last=Mehta|access-date=16 April 2022}}</ref> 1995 में उ पहिला जेटी के स्थापना कइले। मूल रूप से मुंद्रा पोर्ट एंड स्पेशल इकोनॉमिक जोन के ओर से संचालित इ संचालन अडानी पोर्ट्स एंड सेज (एपीएसईजेड) के हस्तांतरित क दिहल गइल। आज इ कंपनी सबसे बड़ निजी बहु बंदरगाह संचालक बा। मुंद्रा बंदरगाह भारत के सभसे बड़ निजी क्षेत्र के बंदरगाह बाटे, एकर क्षमता सालाना लगभग 21 करोड़ टन माल ढुलाई करे के क्षमता बाटे।<ref>{{Cite web|last=Manoj|first=P.|title=APSEZ set to become top container port operator|url=https://www.thehindubusinessline.com/economy/logistics/apsez-set-to-become-top-container-port-operator/article9801005.ece|access-date=2021-03-11|website=@businessline| date=3 August 2017 |language=en}}</ref><ref>{{cite news|title=Adani becomes first Indian port operator to record 200 MT cargo movement|work=The Economic Times|url=https://economictimes.indiatimes.com/industry/transportation/shipping-/-transport/adani-becomes-first-indian-port-operator-to-record-200-mt-cargo-movement/articleshow/68592301.cms?from=mdr|access-date=2021-03-11}}</ref> 1996 में अडानी समूह के पावर बिजनेस शाखा अडानी पावर के स्थापना अडानी कइले रहले। अडानी पावर के लगे 4620 मेगावाट के क्षमता वाला थर्मल पावर प्लांट बाड़ें, ई देश के सभसे बड़ निजी थर्मल पावर उत्पादक कंपनी हवे।<ref>{{cite news|url=https://economictimes.indiatimes.com/industry/energy/power/abb-links-648-mw-solar-project-at-kamuthi-with-national-grid/articleshow/52728908.cms|title=Adani Solar Project|date=2016-06-13|publisher=economictimes.com}}</ref> 2006 में अडानी बिजली उत्पादन के कारोबार में उतरले। साल 2009 से 2012 ले ऊ ऑस्ट्रेलिया के एबट प्वाइंट पोर्ट आ क्वींसलैंड के कारमाइकल कोयला खदान के अधिग्रहण कइलें।<ref>{{cite news|url=https://www.outlookbusiness.com/specials/the-boss/gautam-adani-1237|title=Gautam Adani, chairman Adani group|date=2015-07-10|publisher=outlookbusiness.com|access-date=2022-06-24|archive-date=2021-03-07|archive-url=https://web.archive.org/web/20210307025714/https://outlookbusiness.com/specials/the-boss/gautam-adani-1237|url-status=dead}}</ref> मई 2020 में अडानी भारत के सौर ऊर्जा निगम (एसईसीआई) के ओर से 6 अरब अमेरिकी डॉलर के दुनिया के सबसे बड़ सौर बोली जीतले रहे। 8000 मेगावाट के फोटोवोल्टिक बिजली संयंत्र परियोजना के अडानी ग्रीन ले लीहें; अडानी सोलर 2000 मेगावाट के अतिरिक्त सोलर सेल आ मॉड्यूल निर्माण क्षमता के स्थापना करी।<ref>{{cite news|last=Reporter|first=S. I.|date=2020-06-09|title=Adani Green hits new high on winning world's largest solar bid worth $7 bn|work=Business Standard India|url=https://www.business-standard.com/article/markets/adani-green-hits-new-high-stock-zooms-127-in-three-months-120060900281_1.html|access-date=2020-06-10}}</ref><ref>{{Cite web|date=2020-06-09|title=Adani wins world's largest solar project; to invest Rs 45,000 crore|url=https://www.financialexpress.com/industry/adani-wins-worlds-largest-solar-project-to-invest-rs-45000-crore/1986420/|access-date=2020-06-10|website=The Financial Express|language=en-US}}</ref> सितंबर 2020 में अडानी मुंबई अंतर्राष्ट्रीय हवाई अड्डा में 74% हिस्सेदारी हासिल कइलें, ई दिल्ली के बाद भारत के दुसरा सभसे व्यस्त हवाई अड्डा हवे।<ref>{{Cite web|last=Karmali|first=Naazneen|title=India's 10 Richest Billionaires 2021|url=https://www.forbes.com/sites/naazneenkarmali/2021/04/06/indias-10-richest-billionaires-2021/|access-date=2021-07-01|website=Forbes|language=en}}</ref> फरवरी 2022 में ऊ एशिया के सभसे धनी ब्यक्ति बनलें आ मुकेश अंबानी के पीछे छोड़ दिहलें।<ref name="forbes1">{{cite web |last=Pratap |first=Aayushi |date=3 February 2022 |title=Indian Billionaire Gautam Adani Becomes Asia's Richest Person |url=https://www.forbes.com/sites/aayushipratap/2022/02/03/indian-billionaire-gautam-adani-becomes-asias-richest-person/ |access-date=11 February 2022 |work=Forbes}}</ref> मई 2022 में अडानी परिवार अम्बुजा सीमेंट्स आ ओकर सहायक कंपनी एसीसी के होल्सिम ग्रुप से 10.5 बिलियन डॉलर में खरीद लिहलस, एगो बिदेसी स्पेशल-पर्पस इकाई के माध्यम से।<ref>{{Cite web|url=https://timesofindia.indiatimes.com/business/india-business/adani-to-acquire-holcim-india-assets-for-10-5-bn/articleshow/91581073.cms|title=holcim: Adani to acquire Holcim India assets for $10.5 bn - Times of India|website=The Times of India}}</ref> ==निजी जिनगी== गौतम अडानी के बियाह प्रीति से भइल बा, इनहन लोग के दू गो संतान बाड़ें।<ref>{{cite web|url=https://business.mapsofindia.com/business-leaders/gautam-adani.html|title=Gautam Adani |date=2015-06-02|website=mapsofindia.com}}</ref><ref>{{cite news|url=https://ahmedabadmirror.indiatimes.com/columns/the-informer/achtung-baby/articleshow/53066653.cms|title=Priti Adani| date=2016-07-07|website=Ahmedabad mirror}}</ref> ऊ जैन इंटरनेशनल ट्रेड ऑर्गेनाइजेशन (JITO) के सदस्य बाड़ें<ref>{{Cite web | url=https://mumbaimirror.indiatimes.com/mumbai/other/jains-to-organise-trade-fair/articleshow/15652322.cms | title=Jains to organise trade fair|work=Mumbai Mirror|date=10 November 2006|access-date=14 May 2022}}</ref> इनके अपहरण क के 1998 में फिरौती खातिर बंधक बना के रखल गइल, बाकी बिना पइसा लिहले रिहा क दिहल गइल।<ref>{{cite news |title=Throwback: When India's fifth richest man was kidnapped for ransom |url=https://www.timesnownews.com/business-economy/companies/article/throwback-when-indias-fifth-richest-man-survived-a-kidnapping-and-terrorist-attack/606122 |access-date=13 December 2020 |work=Times Now News |date=14 June 2020 |language= en }}</ref><ref>{{cite news |title=Two accused of abducting Gautam Adani 20 years ago acquitted |url=https://indianexpress.com/article/cities/ahmedabad/two-accused-of-abducting-gautam-adani-20-years-ago-acquitted-5473592/ |access-date=13 December 2020 |work=The Indian Express |date=1 December 2018 |location=Ahmedabad |language=en}}</ref> उ 2008 के मुंबई हमला के दौरान ताज होटल में रहले।<ref>{{cite news |last1=Sazonov |first2=Bhuma |last2=Shrivastava |first3=P R |last3=Sanjai |first1=Alexander |title=Billionaire Survivor of Ransom, Terror Attacks Now Rivals Ambani |url=https://www.bloomberg.com/news/articles/2020-12-13/billionaire-survivor-of-ransom-terror-attacks-now-rivals-ambani |access-date=13 December 2020 |work=Bloomberg.com |date=13 December 2020 |language=en}}</ref><ref>{{cite news |last1=Shukla |first1=Nimish |title=Gautam Adani felt safe in Taj toilet |url=https://timesofindia.indiatimes.com/Gautam-Adani-felt-safe-in-Taj-toilet/articleshow/3767246.cms |access-date=13 December 2020 |work=The Times of India |date=28 November 2008 |language=en}}</ref> == बिबाद == === स्टॉक में हेरफेर अउरी लेखा धोखाधड़ी के आरोप === जनवरी 2023 में न्यूयॉर्क शहर के [[हिंडेनबर्ग रिसर्च]] दू साल के जांच के निष्कर्ष प्रकाशित कइलस जेह में दावा कइल गइल कि अडानी बाजार में हेरफेर आ लेखा कदाचार में शामिल रहलें; हिंडेनबर्ग इहो खुलासा कइलस कि ऊ अडानी ग्रुप के कंपनी सभ पर शार्ट पोजीशन रखले बा।<ref>{{cite news |last1=Thomas |first1=Chris |last2=Kalra |first2=Aditya |title=Hindenburg shorts India's Adani citing debt, accounting concerns; shares plunge |url=https://www.reuters.com/markets/asia/hindenburg-says-holds-short-positions-indias-adani-flags-risks-2023-01-25/ |access-date=25 January 2023 |work=[[रायटर्स]] |date=25 January 2023 |language=en}}</ref><ref>{{cite news |last1=Sorkin |first1=Andrew Ross |last2=Mattu |first2=Ravi |last3=Warner |first3=Bernhard |last4=Kessler |first4=Sarah |last5=Merced |first5=Michael J. de la |last6=Hirsch |first6=Lauren |last7=Livni |first7=Ephrat |title=A Short Seller Takes Aim at an Indian Corporate Giant |url=https://www.nytimes.com/2023/01/25/business/dealbook/short-seller-hindenburg-adani.html |access-date=25 January 2023 |work=[[द न्यूयार्क टाइम्स]] |date=25 January 2023}}</ref><ref>{{cite news |last1=Lockett |first1=Hudson |title=Adani shares take $10.8bn hit after Hindenburg bets against group |url=https://www.ft.com/content/4aedaaf9-9ee8-4334-945c-12ac08429d6a |access-date=25 January 2023 |work=[[फ़ाइनान्शियल टाइम्स]] |date=25 January 2023}}</ref> आरोप के बाद अडानी से जुड़ल कंपनी सभ के बांड आ शेयर सभ के कीमत में गिरावट आइल आ क्रेडिट सुइस अडानी ग्रुप के बांड सभ के जमानत के रूप में स्वीकार कइल बंद क दिहलस।<ref>{{cite news |last1=Lee |first1=Jihye |title=Adani shares fall as short seller firm Hindenburg announces short position |url=https://www.cnbc.com/2023/01/25/adani-group-hindenburg-short-position.html |access-date=25 January 2023 |work=[[CNBC]] |date=25 January 2023 |language=en}}</ref><ref>{{cite news |last1=Kay |first1=Chris |last2=Vishnoi |first2=Abhishek |last3=Joshi |first3=Ashutosh |title=Hindenburg Targets Asia’s Richest Man, Triggering Adani Selloff |url=https://www.bloomberg.com/news/articles/2023-01-25/adani-group-stocks-drop-after-hindenburg-takes-short-position |access-date=25 January 2023 |work=[[Bloomberg News]] |date=25 January 2023 |language=en}}</ref><ref>https://www.npr.org/2023/02/02/1153420761/adani-group-hindenburg</ref> अडानी धोखाधड़ी के आरोप सभ के बेबुनियाद आ बुरा इरादा बता के इनकार कइलें आ इनहन के "दुर्भावनापूर्ण" आ "चयनात्मक गलत जानकारी" बतवलें।<ref name="madhok-2023"> {{cite news | last1 = Madhok | first1 = Diksha | title = Asia's richest man slams short-seller's fraud claims as 'baseless' and 'malicious' | date = 25 January 2023 | language = en | work = CNN Business | location = Atlanta, Georgia, USA | url = https://www.cnn.com/2023/01/25/business/india-adani-shares-plunge-hindenburg-report-hnk-intl/index.html | access-date = 25 January 2023 }} </ref><ref>{{Cite news |date=2023-01-26 |title=Adani Group: Asia's richest man hits back at 'con' allegations |language=en-GB |work=BBC News |url=https://www.bbc.com/news/business-64408720 |access-date=2023-01-28}}</ref> एकरे बाद रिपोर्ट के तीन दिन बाद अडानी द्वारा नियंत्रित सूचीबद्ध कंपनी सभ के शेयर सभ के बजार कीमत में 48 बिलियन डॉलर के नुकसान भइल।<ref>{{Cite web |title=Gautam Adani: One of the world's richest men loses ground after £39bn stock market slump |url=https://news.sky.com/story/gautam-adani-one-of-the-worlds-richest-men-loses-ground-after-39bn-stock-market-slump-12796545 |access-date=2023-01-28 |website=स्काई न्यूज |language=en}}</ref> ==फिलेंथ्रोपिस्ट के रूप में== अडानी अडानी फाउंडेशन के अध्यक्ष बाड़े, जवना के फंडिंग अडानी ग्रुप के माध्यम से कइल जाला। एकर स्थापना 1996 में भइल।गुजरात के अलावा ई फाउंडेशन [[महाराष्ट्र]], [[राजस्थान]], हिमाचल प्रदेश, [[मध्य प्रदेश]], छत्तीसगढ़, आ ओडिशा राज्य सभ में काम करे ला।<ref>{{Cite web|title=Adani Foundation {{!}} Youth4work|url=https://www.university.youth4work.com/adani-foundation|access-date=2020-12-07|website=youth4work.com|language=en|archive-date=2022-03-31|archive-url=https://web.archive.org/web/20220331015615/https://www.university.youth4work.com/adani-foundation|url-status=dead}}</ref> मार्च 2020 में, ऊ अपना समूह के परोपकार शाखा के माध्यम से पीएम केयर्स फंड में ₹100 करोड़ (13 मिलियन अमेरिकी डॉलर) के योगदान दिहलें, कोरोना वायरस के प्रकोप से लड़े खातिर।<ref>{{Cite web|url=https://www.indiatvnews.com/business/news-coronavirus-gautam-adani-donation-rs-1000-crore-pm-fund-602679|title=Gautam Adani gives Rs 100 crore to PM Fund to fight coronavirus|last=Joshi|first=Manas|date=2020-03-29|website=www.indiatvnews.com|language=en|access-date=2020-04-01}}</ref> गुजरात सीएम राहत कोष में ₹5 करोड़ (660,000 अमेरिकी डॉलर) आ महाराष्ट्र सीएम राहत कोष में ₹1 करोड़ (130,000 अमेरिकी डॉलर) के योगदान दिहल गइल।<ref>{{cite news|url=https://economictimes.indiatimes.com/news/company/corporate-trends/billionaire-gautam-adani-commits-to-support-fight-against-coronavirus/articleshow/74983932.cms|title=Billionaire Gautam Adani commits to support fight against coronavirus|date=2020-04-04|work=The Economic Times|access-date=2020-04-10}}</ref> अडानी के नेतृत्व में विविध समूह अडानी समूह सऊदी अरब के दम्माम से गुजरात के मुंड्रा में 80 मीट्रिक टन तरल मेडिकल ऑक्सीजन से भरल चार आईएसओ क्रायोजेनिक टैंक के आयात कइलस। संगही, इ समूह लिंडे सऊदी अरब से 5000 मेडिकल ग्रेड के ऑक्सीजन सिलेंडर भी हासिल कइले बिया। एगो ट्विटर पोस्ट में अडानी साझा कइले कि, उनुकर समूह गुजरात के कच्छ जिला में जहां-जहां जरूरत बा, उहाँ हरेक दिन उनुकर ग्रुप 1500 सिलेंडर के मेडिकल ऑक्सीजन के सप्लाई करता।<ref>{{Cite web|last=My Nation|title=To improve oxygen availability, Adani group imports cryogenic tanks from Saudi Arabia|url=https://www.mynation.com/india-news/to-improve-oxygen-availability-adani-group-imports-cryogenic-tanks-from-saudi-arabia-qs47sk|url-status=live|access-date=2021-05-11|website=Asianet News Network Pvt Ltd|language=en}}</ref> == संदर्भ == {{Reflist|33em}} [[श्रेणी:जियत लोग]] [[श्रेणी:1964 में जनम]] [[श्रेणी:भारतीय बिजनेसमैन]] a8j3ra9vjw8z8wgnus8h3qv3gtiqu49 आटा 0 60493 803289 786883 2026-07-29T16:06:02Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803289 wikitext text/x-wiki [[File:All-Purpose Flour (4107895947).jpg|thumb|आटा]] '''आटा''' चाहे '''पिसान''' कई किसिम क [[अनाज]], [[फल]] या [[जड़]] के महिन बुरादा (पाउडर) ह। अनाज, फल या जड़ के [[मील]] या [[जाँता]] या [[ओखर]] में पीस/कूट के आटा प्राप्त कइल जाला। आटा से [[रोटी]] आ विभिन्न प्रकार के व्यंजन (खाये वाली चीज) बनावल जाला। <ref>{{cite web |url= http://www.hindi2dictionary.com/%E0%A4%86%E0%A4%9F%E0%A4%BE-meaning-hindi.html |title= आटा meaning in hindi |date= |website= www.hindi2dictionary.com |publisher= |access-date= 17 May 2018 |archive-date= 28 April 2018 |archive-url= https://web.archive.org/web/20180428214010/http://www.hindi2dictionary.com/%E0%A4%86%E0%A4%9F%E0%A4%BE-meaning-hindi.html |url-status= dead }}</ref> [[अन्न]] के आटा से रोटी आ ब्रेड तैयार कइल जाऽला, जवन हमनी के रोज के खाना में शामिल बा। [[गेंहू के आटा]] [[ओशिनिया]], [[यूरोप]], [[दक्खिन अमेरिका]], [[उत्तर अमेरिका]], [[मध्य पूरुब]], [[उत्तर भारत]] आ [[उत्तर अफ्रीका]] के संस्कृति में पावल जाला, जहां लोग अपन-अपन संस्कृति के हिसाब से रोटी आ पेस्ट्री (आटा से बनल मिठाई) बनावेला लोग। ==आटा के कुछ प्रकार== * गेंहू के आटा (wheat flour) अनाज से बनेला। * चावल के आटा (rice flour) अनाज से बनेला * [[बेसन]] चाहे चना क आटा * मकई के आटा (corn flour) अनाज से * नारियल के आटा (coconut flour) फल से * केरा के आटा (banana flour) फल से * कसावा के आटा (cassava flour) जड़ से बनेला ==संदर्भ== {{Reflist}} ==बाहरी लिंक== * [http://www.thisisinsider.com/how-bread-is-eaten-in-27-countries-around-the-world-2016-10 How bread is eaten in 27 countries around the world] [[श्रेणी:खाना बनावे में इस्तमाल होखे वाला सामग्री]] {{food-stub}} bhas9n7nwn8u2mbjjmvkexs70axxx0j पहिला बिस्व जुद्ध 0 65676 803310 790815 2026-07-29T16:19:58Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803310 wikitext text/x-wiki {{Infobox military conflict | conflict = [[World War I]] | image = WWImontage.jpg | image_size = 300px | caption = '''Clockwise from the top''': The aftermath of shelling during the [[Battle of the Somme]], [[Mark V tank]]s cross the [[Hindenburg Line]], {{HMS|Irresistible|1898|6}} sinks after hitting a [[naval mine|mine]] in the [[Naval operations in the Dardanelles Campaign|Dardanelles]], a British [[Vickers machine gun]] crew wears [[gas mask]]s during the Battle of the Somme, [[Albatros D.III]] fighters of [[Jagdstaffel 11]] | date = {{start and end dates|1914|7|28|1918|11|11|df=yes}}<br />({{Age in years, months, weeks and days|month1=7|day1=28|year1=1914|month2=11|day2=11|year2=1918}}) {{Collapsible list | bullets = yes | title = Peace treaties |[[Treaty of Versailles]]<br />Signed 28 June 1919<br />({{Age in years, months, weeks and days|month1=7|day1=28|year1=1914|month2=6|day2=28|year2=1919}}){{efn|The United States did not ratify any of the treaties agreed to at the [[Paris Peace Conference, 1919|Paris Peace Conference]].}}<!--Since World War I didn't actually end for most states until the Paris Peace Conference treaties were signed...--> |[[Treaty of Saint-Germain-en-Laye (1919)|Treaty of Saint-Germain-en-Laye]]<br />Signed 10 September 1919<br />({{Age in years, months, weeks and days|month1=7|day1=28|year1=1914|month2=9|day2=10|year2=1919}}) |[[Treaty of Neuilly-sur-Seine]]<br />Signed 27 November 1919<br />({{Age in years, months, weeks and days|month1=10|day1=14|year1=1915|month2=11|day2=27|year2=1919}}){{efn|Bulgaria joined the Central Powers on 14 October 1915.}} |[[Treaty of Trianon]]<br />Signed 4 June 1920<br />({{Age in years, months, weeks and days|month1=7|day1=28|year1=1914|month2=6|day2=4|year2=1920}}) |[[Treaty of Sèvres]]<br />Signed 10 August 1920<br />({{Age in years, months, weeks and days|month1=7|day1=28|year1=1914|month2=8|day2=10|year2=1920}}){{efn|The Ottoman Empire agreed to a secret alliance with Germany on 2 August 1914. It joined the war on the side of the Central Powers on 29 October 1914.}} |[[U.S.–Austrian Peace Treaty (1921)|United States–Austria Peace Treaty]]<br />Signed 24 August 1921<br />({{Age in years, months, weeks and days|month1=12|day1=7|year1=1917|month2=8|day2=24|year2=1921}}){{efn|The United States [[American entry into World War I#Austria-Hungary|declared war on Austria-Hungary]] on 7 December 1917.}}{{efn|[[First Republic of Austria|Austria]] was considered one of the [[successor states]] to Austria-Hungary.}} |[[U.S.–German Peace Treaty (1921)|United States–Germany Peace Treaty]]<br />Signed 25 August 1921<br />({{Age in years, months, weeks and days|month1=4|day1=6|year1=1917|month2=8|day2=25|year2=1921}}){{efn|The United States [[United States declaration of war on Germany (1917)|declared war on Germany]] on 6 April 1917.}} |[[U.S.–Hungarian Peace Treaty (1921)|United States–Hungary Peace Treaty]]<br />Signed 29 August 1921<br />({{Age in years, months, weeks and days|month1=12|day1=7|year1=1917|month2=8|day2=29|year2=1921}}){{efn|[[Kingdom of Hungary (1920–46)|Hungary]] was considered one of the successor states to Austria-Hungary.}} |[[Treaty of Lausanne]]<br />Signed 24 July 1923<br />({{Age in years, months, weeks and days|month1=10|day1=29|year1=1914|month2=7|day2=24|year2=1923}}){{efn|Although the [[Treaty of Sèvres]] was intended to end the war between the Allied Powers and the Ottoman Empire, the Allied Powers and the [[Republic of Turkey]], the successor state of the Ottoman Empire, agreed to the Treaty of Lausanne.}} }} | place = Europe, Africa, the Middle East, the [[Pacific Islands]], China, Indian Ocean, and off the coast of South and North America | casus = [[Assassination of Archduke Franz Ferdinand]] (28 June) followed by Austro-Hungarian declaration of war on [[Kingdom of Serbia]] (28 July) and Russian mobilisation against [[Austria-Hungary]] (29 July). | territory = * Formation of new countries in Europe and the Middle East * Transfer of [[German colonies]] and [[Partitioning of the Ottoman Empire|regions of the former Ottoman Empire]] to other powers | result = [[Allies of World War I|Allied Powers]] victory * [[Central Powers]]' victory on the [[Eastern Front (World War I)|Eastern Front]] nullified by defeat on the [[Western Front (World War I)|Western Front]] * Fall of all of the continental European ([[German Empire|German]], [[Russian Empire|Russian]], [[Ottoman Empire|Ottoman]], and [[Austria-Hungary|Austro-Hungarian]]) empires * [[Russian Civil War]] and foundation of the [[Soviet Union]] * Establishment of the [[League of Nations]] ([[World War I#Aftermath|more...]]) | combatant1 = '''[[Allies of World War I|Allied Powers]]:'''{{plainlist| * {{flagcountry|French Third Republic}} * {{flagcountry|British Empire}} * {{flagcountry|Russian Empire}}<br />{{small|(1914–17)}} * {{flagcountry|Kingdom of Serbia}} * {{flag|Belgium}} * {{flagcountry|Kingdom of Montenegro}} * {{flagcountry|Empire of Japan}} * {{flagcountry|Kingdom of Italy}}<br />{{small|(1915–18)}} * {{flag|United States|1912}}<br />{{small|(1917–18)}} * {{flagcountry|Kingdom of Romania}}<br />{{small|(1916–18)}} * {{flagcountry|First Portuguese Republic}}<br />{{small|(1916–18)}} * {{flagicon image|Flag of Hejaz (1917).svg}} [[Kingdom of Hejaz|Hejaz]]<br />{{small|(1916–18)}} * {{flagicon image|Flag of China (1912–1928).svg}} [[Republic of China (1912–1949)|China]]<br />{{small|(1917–18)}} * {{flagcountry|Kingdom of Greece}}<br />{{small|(1917–18)}} * {{flagicon|Thailand}} [[Rattanakosin Kingdom (1782–1932)|Siam]]<br />{{small|(1917–18)}} * {{small|[[Allies of World War I|...''and others'']]}}}} | combatant2 = '''[[Central Powers]]:'''{{plainlist| * {{flag|German Empire}} * {{flag|Austria-Hungary}} * {{flag|Ottoman Empire}} * {{flagcountry|Kingdom of Bulgaria}}<br />{{small|(1915–18)}} * {{small|[[Central Powers#Co-belligerents|...''and co-belligerents'']]}}}} | commander1 = '''[[Allied leaders of World War I|Allied Powers leaders]]:'''{{plainlist| * {{flagicon|French Third Republic}} [[Raymond Poincaré]] * {{flagdeco|French Third Republic}} [[Georges Clemenceau]] * {{flagicon|United Kingdom of Great Britain and Ireland|United Kingdom}} [[H. H. Asquith]] * {{flagdeco|United Kingdom of Great Britain and Ireland|United Kingdom}} [[David Lloyd George]] * {{flagicon|Belgium}} [[Albert I of Belgium|Albert I]] * {{flagicon|Russian Empire}} [[Nicholas II of Russia|Nicholas II]] * {{flagicon|Kingdom of Italy}} [[Victor Emmanuel III]] * {{flagdeco|Kingdom of Italy}} [[Vittorio Orlando]] * {{flagicon|United States|1912}} [[Woodrow Wilson]] * {{flagicon|Empire of Japan}} [[Emperor Taishō|Yoshihito]] * {{flagicon|Kingdom of Serbia}} [[Peter I of Serbia|Peter I]] * {{flagicon|Kingdom of Romania}} [[Ferdinand I of Romania|Ferdinand I]] * {{flagicon|Kingdom of Greece}} [[Eleftherios Venizelos]] * {{small|[[Allied leaders of World War I|...''and others'']]}}}} | commander2 = '''[[Leaders of the Central Powers of World War I|Central Powers leaders]]:'''{{plainlist| * {{flagicon|German Empire}} [[Wilhelm II, German Emperor|Wilhelm II]] * {{flagicon|Austria-Hungary}} [[Franz Joseph I]]{{KIA|Death by natural causes}} * {{flagdeco|Austria-Hungary}} [[Charles I of Austria|Karl I]] * {{flagicon|Ottoman Empire}} [[Mehmed V]]{{KIA|Death by natural causes}} * {{flagdeco|Ottoman Empire}} [[Mehmed VI]] * {{flagdeco|Ottoman Empire}} [[Three Pashas]] * {{flagicon|Kingdom of Bulgaria}} [[Ferdinand I of Bulgaria|Ferdinand I]] * {{small|[[Leaders of the Central Powers of World War I|...''and others'']]}}}} | strength1 = {{plainlist| * {{flagicon|Russian Empire}} 12,000,000 * {{flagicon|British Empire}} 8,841,541<ref>{{cite web|url=http://www.1914-1918.net/faq.htm|title=British Army statistics of the Great War|publisher=1914-1918.net|accessdate=13 December 2011|archive-date=2 May 2019|archive-url=https://web.archive.org/web/20190502090525/http://www.1914-1918.net/faq.htm|url-status=dead}}</ref><ref>Figures are for the British Empire</ref> * {{flagicon|French Third Republic}} 8,660,000<ref>Figures are for Metropolitan France and its colonies</ref> * {{flagicon|Kingdom of Italy}} 5,615,140 * {{flagicon|United States|1912}} 4,743,826 * {{flagicon|Kingdom of Romania}} 1,234,000 * {{flagicon|Empire of Japan}} 800,000 * {{flagicon|Kingdom of Serbia}} 707,343 * {{flagicon|Belgium}} 380,000 * {{flagicon|Kingdom of Greece}} 250,000 * {{Flagicon|First Portuguese Republic}} 80,000 * {{flagicon|Kingdom of Montenegro}} 50,000 }} ''Total: 42,959,850''<ref name="Tucker 2005 273">{{harvnb|Tucker|Roberts|2005|p=273}}</ref> | strength2 = {{plainlist| * {{flagicon|German Empire}} 13,250,000 * {{flagicon|Austria-Hungary}} 7,800,000 * {{flagicon|Ottoman Empire}} 2,998,321 * {{flagicon|Kingdom of Bulgaria}} 1,200,000}} ''Total: 25,248,321''<ref name="Tucker 2005 273"/> | casualties1 = {{plainlist| * '''Military dead:''' 5,525,000 * '''Military wounded:''' 12,831,500 * '''Total:''' 18,356,500&nbsp;KIA, WIA and MIA * '''Civilian dead:''' 4,000,000}} {{small|[[World War I casualties|...''further details''.]]}} ---- {{flagicon|French Third Republic}} 1,397,800 killed<ref name="crb.org"/><br /> {{flagicon|British Empire}} 1,114,914 killed<ref name="crb.org">{{Cite web |url=http://www.centre-robert-schuman.org/userfiles/files/REPERES%20%E2%80%93%20module%201-1-1%20-%20explanatory%20notes%20%E2%80%93%20World%20War%20I%20casualties%20%E2%80%93%20EN.pdf |title=आर्काइव कॉपी के बा |access-date=2018-10-19 |archive-date=2021-02-02 |archive-url=https://web.archive.org/web/20210202015715/http://www.centre-robert-schuman.org/userfiles/files/REPERES%20%E2%80%93%20module%201-1-1%20-%20explanatory%20notes%20%E2%80%93%20World%20War%20I%20casualties%20%E2%80%93%20EN.pdf |url-status=dead }}</ref><br /> {{flagicon|Kingdom of Italy}} 651,000 killed<ref name="crb.org"/><br /> {{flagicon|Russian Empire}} 1,811,000 killed<ref name="crb.org"/> <br /> {{flagicon|Kingdom of Romania}} 250,000<ref name="crb.org"/>-335,000 killed<ref name="Nash">{{cite book|last1=Nash|title=Darkest Hours|date=1976|publisher=Rowman & Littlefield|url=https://books.google.com/books?id=3KglCgAAQBAJ&pg=PA775#v=onepage&q&f=false|isbn=9781590775264}}</ref><br /> {{Flagicon|Empire of Japan}} 415 killed<ref name="crb.org"/><br/> {{Flagicon|Kingdom of Serbia}} 275,000 killed<ref name="crb.org"/><br/> {{flagicon|Belgium}} 58,637<ref name="crb.org"/>-87,500 killed<ref name="Nash" /><br />{{flagicon|United States|1912}} 116,708 killed<ref name="crb.org"/><br/> {{Flagicon|Kingdom of Greece}} 26,000 killed<ref name="crb.org"/><br/> {{Flagicon|First Portuguese Republic}} 7,222 killed<ref name="crb.org"/><br/> {{Flagicon|Kingdom of Montenegro}} 3,000 killed<ref name="crb.org"/> | casualties2 = {{plainlist| * '''Military dead:''' 4,386,000 * '''Military wounded:''' 8,388,000 * '''Total:''' 12,774,000&nbsp;KIA, WIA and MIA * '''Civilian dead:''' 3,700,000}} {{small|[[World War I casualties|...''further details''.]]}} ---- {{flagicon|German Empire}} 2,050,897 killed<ref name="crb.org"/><br />{{flagicon|Austria-Hungary}} 1,200,000 killed<ref name="Nash" /><br />{{flagicon|Ottoman Empire}} 771,844 killed<ref name="crb.org"/><br/>{{flagicon|Kingdom of Bulgaria}} 87,500 killed<ref name="crb.org"/> | campaignbox = {{Campaignbox World War I}} }} '''पहिला बिस्व जुद्ध''' (World War I) 28 जुलाई 1914 से 11 नवंबर 1918 ले चलल रहे। ई जुद्ध आज तक के सभसे बड़ जुद्धन मे ले एगो बाटे। {{clear}} ==नोट== <references group="नोट"/> == नाँव == ''विश्व युद्ध'' शब्द के पहिला बेर सितंबर १९१४ में जर्मन जीवविज्ञानी अउरी दार्शनिक अर्नस्ट हेकेल द्वारा गढ़ल गइल रहे। उ दावा कइले कि, "एह में कवनो संदेह नइखे कि आशंकित 'यूरोपीय युद्ध' के रस्ता अउरी चरित्र।"&nbsp;... शब्द के पूरा अर्थ में पहिला बिस्व जुद्ध बन जाई," {{Sfn|Shapiro|Epstein|2006|p=329}} २० सितंबर १९१४ के ''द इंडियानापोलिस स्टार'' में। '''पहिला बिस्व जुद्ध''' शब्द (अक्सर संक्षिप्त रूप से '''WWI''' या '''WW1''' ), लेफ्टिनेंट कर्नल द्वारा इस्तेमाल कइल गइल रहे। चार्ल्स à कोर्ट रेपिंगटन, उनके संस्मरण सभ खातिर टाइटिल के रूप में (१९२० में प्रकाशित); ऊ १० सितंबर १९१८ के अपना डायरी एंट्री में हार्वर्ड विश्वविद्यालय के एगो मेजर जॉनस्टोन के साथे एह मामिला पर आपन चर्चा के नोट कइले रहलें <ref>{{Cite news|url=https://public.oed.com/blog/june-2014-update-chief-editors-notes-june-2014/|title=Chief Editor's notes June 2014|last=Proffitt|first=Michael|date=2014-06-13|work=Oxford English Dictionary's blog}}</ref> [[दुसरा बिस्व जुद्ध|द्वितीय विश्वयुद्ध]] से पहिले १९१४–१९१८ के घटना के आम तौर पर '''महायुद्ध''' या खाली '''विश्वयुद्ध के''' नाम से जानल जात रहे .<ref>{{Cite web|url=http://www.history.com/news/ask-history/were-they-always-called-world-war-i-and-world-war-ii|title=Were they always called World War I and World War II?|website=Ask History|archive-url=https://web.archive.org/web/20151001001745/http://www.history.com/news/ask-history/were-they-always-called-world-war-i-and-world-war-ii|archive-date=1 October 2015|access-date=24 October 2013}}</ref> {{Sfn|Braybon|2004|p=8}} अगस्त १९१४ में ''द इंडिपेंडेंट'' पत्रिका "ई महान युद्ध ह" लिखलस। ई आपन नाँव रखे ला"। <ref name="independent19140817">{{Cite magazine |date=1914-08-17 |title=The Great War |url=https://archive.org/details/independen79v80newy/page/n233/mode/1up?view=theater |magazine=The Independent |page=228 |access-date=2022-05-17}}<cite class="citation magazine cs1 cs1-prop-jul-greg-uncertainty" data-ve-ignore="true">[https://archive.org/details/independen79v80newy/page/n233/mode/1up?view=theater "The Great War"]. ''The Independent''. 17 August 1914. p.&nbsp;228<span class="reference-accessdate">. Retrieved <span class="nowrap">17 May</span> 2022</span>.</cite> </ref> अक्टूबर १९१४ में कनाडा ''के पत्रिका मैकलिन'' एही तरे लिखले रहे कि, "कुछ युद्ध अपना नाम के नाम लेला।" ई महायुद्ध हवे।" <ref>{{Cite web|url=http://www.oed.com/view/Entry/81104|title=great, adj., adv., and n|website=Oxford English Dictionary|url-access=subscription|archive-url=https://web.archive.org/web/20190514194006/https://www.oed.com/view/Entry/81104|archive-date=14 May 2019|access-date=19 March 2012}}<cite class="citation web cs1" data-ve-ignore="true"><span class="cs1-lock-subscription" title="Paid subscription required">[http://www.oed.com/view/Entry/81104 "great, adj., adv., and n"]</span>. ''Oxford English Dictionary''. [https://web.archive.org/web/20190514194006/https://www.oed.com/view/Entry/81104 Archived] from the original on 14 May 2019<span class="reference-accessdate">. Retrieved <span class="nowrap">19 March</span> 2012</span>.</cite></ref> समकालीन यूरोपीय लोग एकरा के " युद्ध खतम करे वाला लड़ाई " भी कहल आ एकरे तब के बेजोड़ पैमाना, तबाही आ जान के नुकसान के बारे में इनहन के धारणा के कारण एकरा के "सब युद्ध सभ के खतम करे वाला लड़ाई" भी बतावल गइल। <ref>{{Cite news|url=http://news.bbc.co.uk/2/hi/special_report/1998/10/98/world_war_i/198172.stm|title=The war to end all wars|date=10 November 1998|work=BBC News|access-date=15 December 2015|archive-url=https://web.archive.org/web/20150619035838/http://news.bbc.co.uk/2/hi/special_report/1998/10/98/world_war_i/198172.stm|archive-date=19 June 2015}}<cite class="citation news cs1" data-ve-ignore="true">[http://news.bbc.co.uk/2/hi/special_report/1998/10/98/world_war_i/198172.stm "The war to end all wars"]. ''BBC News''. 10 November 1998. [https://web.archive.org/web/20150619035838/http://news.bbc.co.uk/2/hi/special_report/1998/10/98/world_war_i/198172.stm Archived] from the original on 19 June 2015<span class="reference-accessdate">. Retrieved <span class="nowrap">15 December</span> 2015</span>.</cite></ref> विश्वयुद्ध के बाद{{Spaces}}II १९३९ में शुरू भइल, ई शब्द अउरी मानक हो गइलें, कनाडा के लोग समेत ब्रिटिश साम्राज्य के इतिहासकार लोग "पहिला बिस्व जुद्ध" आ अमेरिकी लोग "विश्वयुद्ध" के पक्ष में रहल{{Spaces}}I".<ref>Margery Fee and Janice McAlpine. ''Guide to Canadian English Usage''. (Oxford UP, 1997), p. 210.</ref> == पृष्ठभूमि == {{Main|Causes of World War I}} === राजनीतिक आ सैन्य गठबंधन के बात कइल जाव === [[File:Map_Europe_alliances_1914-en.svg|alt=Map of Europe focusing on Austria-Hungary and marking the central location of ethnic groups in it including Slovaks, Czechs, Slovenes, Croats, Serbs, Romanians, Ukrainians, Poles.|thumb|१९१४ में प्रतिद्वंद्वी सैन्य गठबंधन: हरियर रंग में ट्रिपल एंटेन्टे ; भूरे रंग में ट्रिपल एलायंस के बा । खाली ट्रिपल एलायंस एगो औपचारिक "गठबंधन" रहे; बाकी लोग के सूचीबद्ध समर्थन के अनौपचारिक पैटर्न रहे।]] १९वीं शदी के बहुत सारा हिस्सा ले यूरोपीय के प्रमुख ताकत सभ आपस में सत्ता के क्षीण संतुलन बनवले रखलीं जेकरा के यूरोप के संगीत कार्यक्रम के नाँव से जानल जाला। {{Sfn|Clark|2013|pp=121–152}} १८४८ के बाद एकरा के कई किसिम के कारण चुनौती दिहल गइल जवना में ब्रिटेन के तथाकथित शानदार अलगाव में वापसी उस्मानी साम्राज्य के पतन आ ओटो वॉन बिस्मार्क के समय में प्रशिया के उदय शामिल रहल। १८६६ के आस्ट्रिया-प्रशियाई लड़ाई से [[जर्मनी]] में प्रशियाई वर्चस्व के स्थापना भइल जबकि १८७०–१८७१ के फ्रांको-प्रशियाई लड़ाई में जीत के चलते बिस्मार्क के जर्मन राज्य सभ के प्रशियाई नेतृत्व में जर्मन साम्राज्य में एकट्ठा करे के इजाजत मिलल। १८७१ के हार के बदला लिहल या फिर प्रतिशोध आ अल्सास-लोरेन के प्रांत सब के बरामद कइल अगिला चालीस साल ले फ्रांस के नीति के प्रमुख विसय बन गइल। {{Sfn|Zeldin|1977|p=117}} फ्रांस के अलग-थलग करे आ दूगो मोर्चा पर लड़ाई से बचे खातिर बिस्मार्क आस्ट्रिया-हंगरी, [[रूसी साम्राज्य|रूस]] आ जर्मनी के बीच तीन सम्राट के लीग (जर्मन: ''Dreikaiserbund'' ) के बातचीत कइलें। १८७७–१८७८ के रूस-तुर्की लड़ाई में रूस के जीत के बाद, बाल्कन में रूस के परभाव के बारे में ऑस्ट्रिया के चिंता के कारण लीग के भंग क दिहल गइल, ई क्षेत्र ई लोग सब हुत महत्व के सामरिक रुचि के रूप में मानत रहल। एकरे बाद जर्मनी आ ऑस्ट्रिया-हंगरी १८७९ के ड्यूल एलायंस के गठन कइलें, ई ट्रिपल एलायंस बन गइल जब इटली १८८२ में सामिल भइल। {{Sfn|Keegan|1998|p=52}} बिस्मार्क खातिर एह समझौता सभ के मकसद ई रहल कि तीनों साम्राज्य सभ के बीच कौनों बिबाद के समाधान सुनिश्चित कऽ के फ्रांस के अलग कइल जाय जब १८८० में रूस के साथे सीधे बातचीत करे के ब्रिटिश आ फ्रांसीसी कोसिस से एकरा के खतरा पैदा भइल तब ऊ १८८१ में लीग में सुधार कइलें जेकर नवीकरण १८८३ आ १८८५ में भइल। १८८७ में ई समझौता खतम भइला के बाद ऊ एकरे जगह पुनर्बीमा संधि रखलें, ई जर्मनी आ रूस के बीच एगो गुप्त समझौता रहे कि अगर फ्रांस भा आस्ट्रिया-हंगरी में से कवनो एक पर हमला होखे तब तटस्थ रहे के चाहीं। {{Sfn|Medlicott|1945|pp=66–70}} बिस्मार्क रूस के साथे शांति के जर्मनी के बिदेस नीति के आधार मानत रहले लेकिन १८९० में कैसर बनला के बाद विल्हेम द्वितीय उनका के रिटायर होखे खातिर मजबूर कइले अउरी उनकर नयका चांसलर लियो वॉन कैप्रीवी द्वारा पुनर्बीमा संधि के नवीकरण ना करे खातिर मना लिहल गइल| {{Sfn|Keenan|1986|p=20}} 15 '']'' . _ जबकि ई औपचारिक गठबंधन ना रहलें, [[अफिरका]] आ एशिया में लंबा समय से चलल आ रहल औपनिवेशिक बिबाद सभ के निपटारा कऽ के भविष्य में फ्रांस भा रूस से जुड़ल कौनों भी संघर्ष में ब्रिटिश लोग के प्रवेश के संभावना बन गइल। {{Sfn|Fay|1930|pp=290–293}} {{Sfn|Willmott|2003|p=15}} १९११ में अगादिर संकट के दौरान जर्मनी के खिलाफ फ्रांस के ब्रिटिश आ रूस के समर्थन से इनहन लोग के संबंध मजबूत भइल आ एंग्लो-जर्मन बिछुड़न बढ़ल आ १९१४ में पैदा होखे वाला बिभाजन अउरी गहिराह हो गइल। === हथियार के दौड़ बा === १८७१ के बाद जर्मन औद्योगिक ताकत में काफी बढ़ती भइल, एकीकृत रीख के निर्माण, फ्रांसीसी क्षतिपूर्ति के भुगतान आ अल्सास-लोरेन के बिलय के कारण। विल्हेम द्वितीय के समर्थन से एडमिरल अल्फ्रेड वॉन तिरपिट्ज आर्थिक शक्ति में एह बढ़ती के परयोग ''कैसरलिचे मरीन'', या इंपीरियल जर्मन नेवी के निर्माण में करे के कोसिस कइलें जे बिस्व नौसैनिक वर्चस्व खातिर ब्रिटिश रॉयल नेवी के साथ मुकाबला कऽ सके। {{Sfn|Willmott|2003|p=21}} इनके सोच पर अमेरिकी नौसेना के रणनीतिकार अल्फ्रेड थायर महान के परभाव पड़ल, इनके तर्क रहल कि ग्लोबल पावर प्रोजेक्शन खातिर नीला पानी के नौसेना के कब्जा बहुत जरूरी बा; तिरपिट्ज अपना किताबन के जर्मन में अनुवाद करावत रहले जबकि विल्हेम अपना सलाहकारन आ वरिष्ठ सैन्य कर्मी लोग खातिर एह किताबन के पढ़े के जरूरत बनवले रहले। {{Sfn|Herwig|1988|pp=72–73}} हालांकि ई एगो भावनात्मक फैसला भी रहे, जवना के चलते विल्हेम के एक संगे रॉयल नेवी के प्रशंसा अउरी एकरा से आगे निकले के इच्छा रहे। बिस्मार्क के गणना रहे कि जब तक ओकर समुद्री वर्चस्व सुरक्षित रही तब तक ब्रिटेन यूरोप में हस्तक्षेप ना करी लेकिन १८९० में उनुका बर्खास्तगी के चलते नीति में बदलाव भइल अउरी एंग्लो-जर्मन नौसैनिक हथियार के दौड़ शुरू भइल | {{Sfn|Moll|Luebbert|1980|pp=153–185}} तिरपिट्ज द्वारा खर्च कइल गइल भारी रकम के बावजूद, {{HMS|Dreadnought|1906|6}} के लॉन्च{{HMS|Dreadnought|1906|6}} १९०६ में अंग्रेजन के अपना जर्मन प्रतिद्वंद्वी के मुकाबले तकनीकी फायदा देले रहे जवना के ऊ लोग कबो ना हारल| {{Sfn|Willmott|2003|p=21}} अंत में ई दौड़ बिसाल संसाधन सभ के मोड़ के जर्मन नौसेना के निर्माण कइलस जे ब्रिटेन के बिरोध करे खातिर पर्याप्त रहल, बाकी एकरा के ना हरा सके; १९११ में चांसलर थियोबाल्ड वॉन बेथमैन होलवेग हार के कबूल कइलें जेकरा चलते ''रुस्टंगस्वेंडे'' भा 'हथियार के मोड़' बन गइल, जब ऊ नौसेना से खरचा से सेना में बदल दिहलें। {{Sfn|Stevenson|2016|p=45}} ई फैसला राजनीतिक तनाव में कमी के वजह से ना भइल रहे बल्कि १९०५ के रूस-जापानी युद्ध अउरी ओकरा बाद के क्रांति में रूस के हार से उबरला के लेके जर्मनी के चिंता रहे| फ्रांस के फंडिंग के समर्थन से आर्थिक सुधार के कारण १९०८ के बाद रेलवे आ बुनियादी ढांचा के काफी बिस्तार भइल, खासतौर पर एकरे पच्छिमी सीमावर्ती क्षेत्र सभ में। {{Sfn|Crisp|1976|pp=174–196}} चूँकि जर्मनी आ ऑस्ट्रिया-हंगरी रूस के तुलना में अपना संख्यात्मक हीनता के भरपाई करे खातिर तेजी से जुटान पर निर्भर रहलें, एह अंतर के बंद होखे से पैदा होखे वाला खतरा रॉयल नेवी के मुकाबला करे से ढेर महत्व के रहल। जर्मनी के १९१३ में आपन खड़ा सेना के १७०,००० सैनिकन के बिस्तार कइला के बाद फ्रांस अनिवार्य सैन्य सेवा के दू साल से बढ़ा के तीन साल कऽ दिहलस; अइसने उपाय बाल्कन ताकत आ इटली द्वारा कइल गइल जेकरा चलते उस्मानी आ आस्ट्रिया-हंगरी के खरचा बढ़ल। व्यय के श्रेणीबद्ध करे में अंतर के कारण निरपेक्ष आंकड़ा के गणना कइल मुश्किल बा, काहें से कि एह में अक्सर रेलवे नियर नागरिक बुनियादी ढांचा परियोजना सभ के छोड़ दिहल जाला जेकर सैन्य उपयोग भी रहे। हालाँकि, १९०८ से १९१३ ले छह गो प्रमुख यूरोपीय ताकत सब के रक्षा खरचा में वास्तविक रूप से ५०% से ढेर के बढ़ती भइल। {{Sfn|Stevenson|2016|p=42}} === बाल्कन में टकराव के चलते === [[File:1908-10-07_-_Moritz_Schiller's_Delicatessen.jpg|alt=Photo of large white building with one signs saying "Moritz Schiller" and another in Arabic; in front is a cluster of people looking at poster on the wall.|right|thumb|1908 में ऑस्ट्रिया के विलय के घोषणा वाला पोस्टर पढ़त साराजेवो के नागरिक]] १९१४ से पहिले के सालन में बाल्कन में संकट के सिलसिला सुरू भइल काहें से कि अउरी ताकत सभ उस्मानी पतन से फायदा उठावे के कोसिस करत रहलें। जबकि पैन स्लाविक अउरी रूढ़िवादी रूस अपना के सर्बिया अउरी स्लाव राज्यन के रक्षक मानत रहे, ऊ लोग सामरिक रूप से महत्वपूर्ण बोस्पोरस जलडमरूमध्य पऽ बुल्गारिया जईसन महत्वाकांक्षी स्लाव सक्ति के बजाय कमजोर उस्मानी सरकार के नियंत्रण कइल पसन्द करत रहे| चूँकि पूर्वी तुर्की में रूस के आपन महत्वाकांक्षा रहे अउरी बाल्कन में ओह लोग के मुवक्किलन के दावा ओवरलैपिंग रहे एह से ओह लोग के सन्तुलन बनावे से रूसी नीति निर्माता लोग के बंटवारा हो गइल अउरी क्षेत्रीय अस्थिरता अउरी बढ़ गइल| {{Sfn|McMeekin|2015|pp=66–67}} ऑस्ट्रिया के राजनेता लोग बाल्कन के अपना साम्राज्य के निरंतर अस्तित्व खातिर जरूरी आ सर्बिया के विस्तार के सीधा खतरा मानत रहे| १९०८–१९०९ के बोस्निया संकट तब शुरू भइल जब आस्ट्रिया पहिले के उस्मानी क्षेत्र बोस्निया आ हर्जेगोविना के अपना में मिला लिहलस, जवना पर ऊ १८७८ से कब्जा कइले रहे । उस्मानी साम्राज्य से बल्गेरिया के आजादी के घोषणा के साथ मेल खाए के समय पर बनल एह एकतरफा कार्रवाई के यूरोपीय ताकतन द्वारा निन्दा कइल गइल बाकी एकरा के उलट दिहला पर कवनो सहमति ना रहला के कारण एकरा के स्वीकार कइल गइल। कुछ इतिहासकार लोग एकरा के एगो महत्वपूर्ण बढ़ती के रूप में देखे ला, बाल्कन में आस्ट्रिया के रूस के साथे सहयोग करे के कवनो संभावना खतम हो जाला जबकि सर्बिया आ इटली के साथ सम्बन्ध के नुकसान पहुँचावे ला, दुनों के एह क्षेत्र में आपन विस्तारवादी महत्वाकांक्षा रहे। {{Sfn|Clark|2013|p=86}} १९११ से १९१२ तक इटालो-तुर्की युद्ध के बाद तनाव बढ़ल जवना से उस्मानी कमजोरी के परदर्शन भइल अउरी एकरा चलते बाल्कन लीग के गठन भइल जवन सर्बिया, बुल्गारिया, मोंटेनेग्रो अउरी ग्रीस के गठबन्धन रहे| {{Sfn|Clark|2013|pp=251–252}} लीग १९१२ से १९१३ ले के पहिला बाल्कन युद्ध में यूरोपीय तुर्की के अधिकतर हिस्सा के जल्दी से ओवररन कऽ दिहलस, बाहरी पर्यवेक्षक लोग के बहुत आश्चर्य भइभइलल। {{Sfn|McMeekin|2015|p=69}} एड्रियाटिक पर बन्दरगाह सभ पर सर्बियाई कब्जा के परिणाम के रूप में २१ नवंबर १९१२ के आंशिक रूप से ऑस्ट्रिया के जुटान भइल जेह में गैलिसिया में रूसी सीमा के किनारे इकाई सभ भी सामिल रहलें। अगिला दिने एगो बइठक में रूस सरकार एकरा जवाब में जुटान ना करे के फैसला कइलसि काहे कि ऊ अइसन युद्ध के जल्दी करे के तइयार ना रहुवे जवना खातिर ऊ लोग अबहीं तइयार ना रहुवे। {{Sfn|McMeekin|2015|p=73}} महाशक्ति सभ १९१३ के लंदन संधि के माध्यम से नियंत्रण के दोबारा पुष्टि करे के कोसिस कइलस, जेह में एगो स्वतन्त्र [[अल्बानिया|अल्बानिया के]] निर्माण भइल, जबकि बुल्गारिया, सर्बिया, मोंटेनेग्रो आ ग्रीस के इलाका सभ के बिस्तार कइल गइल। हालाँकि, बिजेता लोग के बीच बिबाद के कारण ३३ दिन के दुसरा बाल्कन लड़ाई के सुरुआत भइल, जब बुल्गारिया १६ जून १९१३ के सर्बिया आ ग्रीस पर हमला कइलस; ई हार गइल आ मैसिडोनिया के अधिकतर हिस्सा सर्बिया आ ग्रीस से आ दक्खिनी डोब्रुजा के रोमानिया से हार गइल। {{Sfn|Willmott|2003|pp=2–23}} एकर परिणाम ई भइल कि बाल्कन जुद्ध सभ से फायदा भइल देस सभ, जइसे कि सर्बिया आ ग्रीस, भी अपना "उचित लाभ" से धोखा महसूस कइलें जबकि आस्ट्रिया खातिर ई अइसन उदासीनता के परमान दिहलस जे जर्मनी समेत अउरी ताकत सभ अपना चिन्ता के देखत रहलें। {{Sfn|Clark|2013|p=288}} {{Sfn|Keegan|1998|pp=48–49}} ==संदर्भ== {{Reflist|33em}} [[श्रेणी:CS1: Julian–Gregorian uncertainty]] [[श्रेणी:पहिला बिस्व जुद्ध]] d2nsbids27srnr5umu5mdlwwpc4o3sz पवन सिंह 0 65746 803325 800314 2026-07-29T16:21:02Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803325 wikitext text/x-wiki {{Infobox person | name = पवन सिंह | birth_name = पवन सिंह | image = Pawan_Singh_in_a_concert.jpg | alt = | caption = 2021 में पवन सिंह | birth_date = {{birth date and age|1986|01|05|df=y}}<ref>{{cite web|url=https://www.india.com/buzz/happy-birthday-pawan-singh-from-locker-me-jawani-to-palangiya-sone-na-diya-bhojpuri-superstars-top-songs-that-took-internet-by-storm-in-2018-watch-3512219/|title=जन्मदिन मुबारक पवन सिंह: लॉकर में जवानी ले पलंगिया सोने न दिया, भोजपुरी सुपरस्टार के सबसे हिट गाना जवन २०१८ में धमाल मचा देले रहे – देखऽ|publisher=india.com |date=}}</ref> | birth_place = [[आरा]], [[बिहार]], [[भारत]] | other_names = {{hlist|पॉवर स्टार|शेर सिंह}} | education = स्नातक | alma_mater = [[महाराजा कॉलेज, आरा]] | occupation = {{hlist|[[भोजपुरी एक्टर लोगन के लिस्ट|एक्टर]]|[[भोजपुरी सिंगर लोगन के लिस्ट|गायक]]}} | years_active = {{hlist|1997 - वर्तमान (गायक)|2007 - वर्तमान (अभिनेता)}} | known_for = "लॉलीपॉप लागेलु..." गाना | height_m = 1.75 | spouse = {{Plainlist| * {{marriage|नीलम सिंह |2=2014 |3=2015 |end=d.}} * {{marriage|1=ज्योति सिंह |2=2018}} }} | mother = प्रतिमा सिंह | father = रामा शंकर सिंह | awards = {{hlist|दादासाहेब फाल्के फिल्म फाउंडेशन अवार्ड (2017) |अंतर्राष्ट्रीय भोजपुरी फिल्म पुरस्कार | 2016 में सर्वश्रेष्ठ पुरुष गायक | २०१७ में सर्वश्रेष्ठ अभिनेता | 2017 में सर्वश्रेष्ठ पुरुष गायक | 2018 में सर्वाधिक लोकप्रिय अभिनेता का पुरस्कार | 2019 में फिल्म "मां तुझे सलाम" के लिए सर्वश्रेष्ठ अभिनेता जूरी पुरस्कार}} | module ={{Infobox social media personality | child = yes | youtube_handle = @PawanSinghOfficial009 | youtube_display_name = Pawan Singh Official | youtube_years_active = 2009 –अबतक | youtube_genre = Music | youtube_subscribers = 6.45&nbsp;मिलियन<!-- PLEASE DO NOT CHANGE WITHOUT UPDATING stats_update BELOW --> | youtube_views = 2.131&nbsp;बिलियन<!-- PLEASE DO NOT CHANGE WITHOUT UPDATING stats_update BELOW --> | stats_update = 8 जुलाई 2026 }} | website = {{url|https://pawansingh.co.in/|ऑफिशियल वेबसाइट}} }} '''पवन सिंह''' (जनम: 5 जनवरी 1986) भारतीय [[भोजपुरी सिंगर]] अउरी [[भोजपुरी एक्टर|एक्टर]] बाड़न। इ [[आरा]], [[बिहार]], भारत में पैदा भइलें।<ref>{{Cite web|url=http://timesofindia.indiatimes.com/entertainment/bhojpuri/movies/news/Pawan-Singh-and-Munna-Bhai-together-again/articleshow/44752165.cms|title=पवन सिंह अउरी मुन्नाभाई एक संगे फेंन से|date=9 October 2014|website=टाइम्स ऑफ़ इंडिया|access-date=27 October 2014}}</ref> इ अपना एक्टिंग खाती कई अवार्ड भी जीतले बाड़े, जेमे दू गो अंतरास्ट्रीय भोजपुरी फिलिम पुरस्कार अउर दादा साहेब फाल्के फिलिम फाउंडेशन पुरस्कार सामिल बा। इ भोजपुरी फिल्म में सबसे ज्यादा कमाई करे वाला अभिनेता बाड़न। इ अपन एक्टिंग के शुरुआत ''रंगली चुनरिया तोहरे नाम'' (2014) से शुरू कइलें। == कैरियर == सिंह भोजपुरी पॉप एल्बम में मुख्य गायक के रूप में काम कइले बाड़न। इंकर पहिलका एल्बम ''ओढनिया वालीे'' रहे जवन 1997 में रिलीज़ भइल। 2004 में ''रंगली चुनरिया तोहरे नाम'' से फिलिम में अभिनेता के रूप में पहिलका बेरी अइलन। 2008 में सिंह ''लॉलीपॉप लागेलु'' एल्बम निकालले, जवन कुल्ह दुनिया में तहलका मचइलस। 2016 में इहांके अंतरास्ट्रीय भोजपुरी फिल्म अवार्ड्स में सबसे नीमन मरद गायक के पुरस्कार से सम्मानित कइल गइल।<ref name="International Bhojpuri Film Awards">{{Cite web |url=http://www.bhojpurixp.com/ibfa-awards-winners-list-2016/ |title=जितेवालन के लिस्ट|access-date=10 November 2016 |archive-date=14 May 2018 |archive-url=https://web.archive.org/web/20180514092233/http://www.bhojpurixp.com/ibfa-awards-winners-list-2016/ |url-status=live }}</ref> 2020 में , इ आपन हिंदी डेब्यू कइलन ''"कमरिया हिला रही है"'' गाना ले जेकरा में इनका जरे अमरीकी नचनिया अउरी अभिनेत्री लॉरेन गोटिलेब रहली।<ref>{{cite web|url=https://m.hindustantimes.com/bollywood/lollipop-lagelu-singer-pawan-singh-to-make-hindi-debut-under-jackky-bhagnani-s-label/story-CnSHwzh3xduhhelx3ypX2I_amp.html|title=लॉलीपॉप लागेलु के गायक पवन सिंह आपन हिंदी डेब्यू करे जा रहल बाडे जैकी भगनानी के लेबल में|website=Hindustan Times|access-date=February 20, 2020|archive-date=23 June 2020|archive-url=https://web.archive.org/web/20200623043417/https://m.hindustantimes.com/bollywood/lollipop-lagelu-singer-pawan-singh-to-make-hindi-debut-under-jackky-bhagnani-s-label/story-CnSHwzh3xduhhelx3ypX2I_amp.html|url-status=live}}</ref> == फिलिमोग्राफी == {| class="wikitable" |+फिलिम | style="background:#ffc;"| {{dagger|alt=Films that have not yet been released}} | उ फिलिम जे अभिन ले रिलीज नइखे भइल। |} {| class="wikitable sortable plainrowheaders" |- style="text-align:center;" ! scope="col"|साल ! scope="col"|फिलिम ! scope="col"|रोल ! scope="col"|डाइरेक्टर ! scope="col"|भाषा !टिप्पणियाँ |- | 2007 !scope="row"| ''रंगीले चुनरिया तोहरे नाम'' | राजा |राज कुमार |भोजपुरी | |- | 2008 !scope="row"| ''भोजपुरिया दरोगा'' | पवन |सुदीप पांडे |भोजपुरी | |- | 2008 !scope="row"| ''प्रतिज्ञा (2008 फिलिम)'' | पवन |सुशील उपाध्याय | | |- | 2009 !scope="row"| ''सैया के साथ मदैया माई'' | | आलोक कुमार | | |- | 2009 !scope="row"| ''जोगीजी धीरे धीरे'' | राजा | | | |- | 2009 !scope="row"|''तू जान हौ हमर'' |राज | | | |- | 2009 !scope="row"|''उमरिया कैली तोहरे नाम'' | | | | |- | 2009 !scope="row"|''प्यार बिना चैन कहा'' | | | | |- | 2009 !scope="row"| ''ओढ़निया कमल करे'' | राजा |आलोक कुमार | | |- | 2010 !scope="row"| ''दारार'' | किशन |अनिल सम्राट | | |- | 2011 !scope="row"|''लड़ाई ला अखिया ये लौंडे राजा'' | | | | |- | 2011 !scope="row"|''डोली चड्ढाके दुलहिन ससुराल चली'' | | | | |- | 2010 !scope="row"|''देवरा बड़ा सतावेला'' |राज |राजकुमार आर पांडे | | |- | 2011 !scope="row"| ''रंगबाज दरोगा'' | | | | |- | 2011 !scope="row"| ''कानून हमारा मुठ्ठी में'' | | | | |- | 2011 !scope="row"| ''भागजोगनी'' | | | | |- | 2011 !scope="row"| ''चोरवा बन दमद'' | | | | |- | 2011 !scope="row"|''देवर भाभी'' |राज | | | |- | 2011 !scope="row"|''हमारा मति माई दम बा'' | | | | |- | 2011 !scope="row"|''गुंडई राज'' | | | | |- | 2012 !scope="row"|''सिंदूर दान'' | | | | |- | 2012 !scope="row"| ''जब केहु दिल में समा जला'' | | | | |- |2012 !scope="row"|''सौगंध गंगा मैया के'' |राज | | | |- | 2012 !scope="row"|''रंगबाज राजा'' |राज | | | |- | 2012 !scope="row"|''जंग'' | | | | |- | 2012 !scope="row"|''भैया के साली ओढ़निया वाली'' | राजा |आलोक कुमार | | |- | 2012 !scope="row"| ''एक दूजे के लिए'' | संजू | | | |- | 2013 !scope="row"|''बनारस वाली'' |राज |रमाकांत प्रसाद | | |- | 2013 !scope="row"|''जिद्दी आशिक'' |राज |रमाकांत प्रसाद | | |- | 2013 !scope="row"|''बजरंग'' |बजरंगी | | | |- | 2013 !scope="row"| ''ट्रक चालक'' | राजा |आर राजकुमार पांडेkumar | | |- |2013 !scope="row"| ''कट्टा तनाल दुपट्टा पर'' | राजा |राखी सावंती | | |- | 2013 !scope="row"| ''डकैत'' | | | | |- | 2013 !scope="row"|''देश प्रदेश'' | | | | |- | 2013 !scope="row"|''खून पशिना'' | | | | |- | 2013 !scope="row"|''लावारिश'' | | | | |- | 2013 !scope="row"|''वीर बलवान'' | | | | |- | 2013 !scope="row"|''अंधी तूफ़ान'' | | | | |- | 2013 !scope="row"|''एक और कुरुक्षेत्र'' | | | | |- | 2014 !scope="row"|''करेले कमल धरती के लाल'' | | | | |- | 2014 !scope="row"|''सैयाजी दिलवा मंगले'' |राज |राजकुमार आर पांडे | | |- | 2014 !scope="row"|''योद्धा'' | |राज सिप्पी | | |- | 2014 !scope="row"|''प्रतिज्ञा 2'' |पवन |सुशील कुमार उपाध्याय | | |- | 2014 !scope="row"|''बाजीगर'' | | | | |- | 2014 !scope="row"|''प्यार मोहब्बत जिंदाबाद'' |राज |विनय बिहारी | | |- | 2014 !scope="row"|''इंसाफ'' | | | | |- | 2014 !scope="row"| ''लेबू का जान'' | किशन | | | |- | 2014 !scope="row"|''ठोक देब'' | | | | |- | 2015 !scope="row"|''नहले पे दहला'' | राज |अज्ञात | | |- | 2015 !scope="row"|''बिन बजाये सपेरा'' |राज | | | |- | 2015 !scope="row"|''करज़ विरासत के'' | |अनिल सिंकरी | | |- | 2015 !scope="row"|''बैंड बाजा लेके आजा पवन राजा'' |पवन |दिनेश एस यादव | | |- | 2015 !scope="row"|''लगी छुटे ना राम'' | |जगदीश शर्मा | | |- | 2015 !scope="row"|''संग्राम'' | |जगदीश शर्मा | | |- | 2015 !scope="row"|''गंगापुत्र'' |राज |विनय बिहारी | | |- | 2015 ! scope="row" |''हुकुमत'' | राजा |प्रेम राय | | |- | 2015 ! scope="row" | ''सुहाग'' | राम |अजय श्रीवास्तव | | |- | 2015 ! scope="row"|''बाज गेल डंका'' | शेर सिंह |रवि भूषण | | |- | 2015 !scope="row"|''मुकाबाला'' | | | | |- |2015 !scope="row"|''खून का इल्जाम'' | |सनोज मिश्रा | | |- | 2016 !scope="row"|''भोजपुरिया राजा'' |राज |सुजीत कुमार सिंह | | |- | 2016 !scope="row"|''गदर'' |राज |रमाकांत प्रसादी | | |- | 2016 !scope="row"|''त्रिदेव'' |राज |अरविंद चौबे | | |- | 2016 !scope="row"| ''जिद्दी'' |प्रेम |बालिक | | |- | 2017 !scope="row"|''सरकार राज (भोजपुरी फिलिम 2017)'' |शनि |अरविंद चौबे | | |- | 2017 !scope="row"|''सत्या (भोजपुरी फिलिम 2017)'' |सत्य |सुजीत कुमार सिंह | | |- | 2017 !scope="row"| ''तेरे जैसा यार कहां'' |गोपी |रवि भूषण | | |- | 2017 !scope="row"|''तबाडाला'' |सूर्यकांति |विनोद तिवारी | | |- | 2017 !scope="row"|''धड़कन (2017 फिलिम)'' |शंकरी |सुजीत कुमार सिंह | | |- | 2017 !scope="row"|''चुनौती'' |राज |सतीश जैन | | |- | 2017 !scope="row"|'' योद्धा अर्जुन पंडित'' |अर्जुन पंडित |जगदीश ए. शर्मा | | |- |2018 !scope="row"|''सैया सुपर स्टार'' |पवन |प्रेम राय | | |- | 2018 !scope="row"|''वांटेड'' | |सुजीत कुमार | | |- | 2018 ! scope="row"|''मां तुझे सलाम (2018 फिलिम)'' | बजरंगी |असलम शेख | | |- | 2018 !scope="row"|''बलमुआ तोहरे खतीर'' | |दिनेश यादव | | |- | 2018 ! scope="row"|''लोहा पहलवान'' | |इकबाल बख्शी | | |- | 2018 ! scope="row" | ''राजा (2018 फिलिम)|राजा'' |राज |संजय श्रीवास्तव | | |- | 2019 ! scope="row" | ''क्रैक फाइटर'' |शंकर, सुपर सिंह | सुजीत कुमार सिंह | | |- | 2019 ! scope="row"|''मैंने उन्को साजन चुन लिया'' |विजय |देवेंद्र तिवारी | | |- | 2019 ! scope="row"|''जय हिंद (2019 फिलिम)'' | |फिरोज खान<ref>{{cite news|url=https://m.timesofindia.com/entertainment/bhojpuri/movies/news/jai-hind-pawan-singh-shares-the-new-poster-of-his-upcoming-film/articleshow/70364202.cms | title='जय हिंद': पवन सिंह ने अपनी आने वाली फिलिम का नया पोस्टर शेयर किया जुलाई 2019|archive-date=11 अक्टूबर 2020|archive-url=https://web.archive.org/web/20201011183923/https://timesofindia.indiatimes.com/entertainment/bhojpuri/movies/news/jai-hind|url-status=live}}</ref> | | |- | 2019 !scope="row"| ''शेर सिंह (2019 फिलिम)'' | |शशांक राय | | |- |2021 ! scope="row" style="background:#ffc; " |''घातक'' {{dagger|alt=Films that have not yet been released}} |{{TBA}} | टीनू वर्मा | | |- |2021 ! scope="row" style="background:#ffc; " |''जहरीला'' {{dagger|alt=Films that have not yet been released}} |{{TBA}} |रवि एच कश्यप | | |- |2021 ! scope="row" style="background:#ffc; " | बास (साथ महेश मांजरेकर) |TBA |अरविन्द चौबे | | |- |2021 !हम है राही प्यार के |TBA | | | |} == इहो देखल जाय == * [[भोजपुरी सिनेमा]] * [[भोजपुरी एक्टर लोगन के लिस्ट|भोजपुरी एक्टर]] * [[भोजपुरी सिंगर लोगन के लिस्ट|भोजपुरी गायक]] == संदर्भ == {{Reflist|29em}} [[श्रेणी:जियत लोग]] [[श्रेणी:1986 में जनम]] [[श्रेणी:आरा जिला के लोग]] [[श्रेणी:बिहार के एक्टर]] [[श्रेणी:भारतीय एक्टर]] [[श्रेणी:भोजपुरी सिंगर]] [[श्रेणी:भोजपुरी सिनेमा के मरद एक्टर लोग]] [[श्रेणी:भोजपुरी प्लेबैक सिंगर]] {{Actor-stub}} 9gw8shcbnmyd8fsqd4acxkw13yqn98s Module:Complex date 828 65952 803625 633998 2019-01-24T00:20:51Z 153.185.54.127 Fixed Junk code error 803625 Scribunto text/plain --[[ __ __ _ _ ____ _ _ _ | \/ | ___ __| |_ _| | ___ _ / ___|___ _ __ ___ _ __ | | _____ __ __| | __ _| |_ ___ | |\/| |/ _ \ / _` | | | | |/ _ (_) | / _ \| '_ ` _ \| '_ \| |/ _ \ \/ / / _` |/ _` | __/ _ \ | | | | (_) | (_| | |_| | | __/_| |__| (_) | | | | | | |_) | | __/> < | (_| | (_| | || __/ |_| |_|\___/ \__,_|\__,_|_|\___(_)\____\___/|_| |_| |_| .__/|_|\___/_/\_\ \__,_|\__,_|\__\___| |_| This module is intended for creation of complex date phrases in variety of languages. Once deployed, please do not modify this code without applying the changes first at Module:Complex date/sandbox and testing at Module:Complex date/sandbox/testcases. Authors and maintainers: * User:Sn1per - first draft of the original version * User:Jarekt - corrections and expansion of the original version ]] -- List of external modules and functions local p = {Error = nil} local i18n = require('Module:i18n/complex date') local ISOdate = require('Module:ISOdate')._ISOdate local Calendar = require('Module:Calendar') -- ================================================== -- === Internal functions =========================== -- ================================================== local function langSwitch(list,lang) local langList = mw.language.getFallbacksFor(lang) table.insert(langList,1,lang) table.insert(langList,math.max(#langList,3),'default') for i,language in ipairs(langList) do if list[language] then return list[language] end end local function formatnum1(numStr, lang) -- mostly require('Module:Formatnum').formatNum function used to translate a number to use different numeral characters, -- except that it it does not call that function unless the language is on the list "LList" local LList = {bn=1,bpy=1,kn=1,hi=1,mr=1,new=1,pa=1,gu=1,fa=1,glk=1,mzn=1,ur=1,ar=1,ckb=1,ks=1,lo=1,['or']=1,bo=1,['ml-old']=1,mn=1,te=1,th=1} if LList[lang] then -- call only when the language is on the list numStr = require('Module:Formatnum').formatNum(numStr, lang, 1) end return numStr end local function getISODate(datestr, datetype, lang, num, case) -- translate dates in the format YYYY, YYYY-MM, and YYYY-MM-DD if not case and i18n.Translations[datetype] then -- look up the grammatical case needed and call ISOdate module local rec = langSwitch(i18n.Translations[datetype], lang) if type(rec)=='table' then case = rec.case[num] end end return ISOdate(datestr, lang, case, '', 1) end local function translatePhrase(date1, date2, operation, lang, state) -- use tables in Module:i18n/complex date to translate a phrase if not i18n.Translations[operation] then p.Error = string.format('<span style="background-color:red;">Error in [[Module:Complex date]]: input parameter "%s" is not recognized.</span>', operation or 'nil') return '' end local dateStr = langSwitch(i18n.Translations[operation], lang) if type(dateStr)=='table' then dateStr = dateStr[1] end if type(dateStr)=='function' then local success local nDates = i18n.Translations[operation]['nDates'] if nDates==2 then -- 2 date phrase success, dateStr = pcall(dateStr, date1, date2, state) else -- 1 date phrase success, dateStr = pcall(dateStr, date1, state) end end if type(dateStr)=='string' then -- replace parts of the string '$date1' and '$date2' with date1 and date2 strings dateStr = mw.ustring.gsub(dateStr, '$date1', date1) dateStr = mw.ustring.gsub(dateStr, '$date2', date2) else -- Special case of more complex phrases that can be build out of simple phrases -- If complex case is not translated to "lang" than build it out of simpler ones local x = dateStr dateStr = p._complex_date(x.conj, x.adj1, date1, x.units1, x.era1, x.adj2, date2, x.units2, x.era2, lang, 2) end return dateStr end local function oneDatePhrase(dateStr, adj, era, units, lang, num, case, state) -- translate a single date phrase if num==2 then state.adj, state.era, state.units, state.precision = state.adj2, state.era2, state.units2, state.precision2 end -- dateStr can have many forms: ISO date, year or a number for -- decade, century or millennium if units == '' then -- unit is "year", "month", "day" dateStr = getISODate(dateStr, adj, lang, num, case) else -- units is "decade", "century", "millennium'' dateStr = translatePhrase(dateStr, '', units, lang, state) end -- add adjective ("early", "mid", etc.) or preposition ("before", "after", -- "circa", etc.) to the date if adj ~= '' then dateStr = translatePhrase(dateStr, '', adj, lang, state) else -- only era? dateStr = formatnum1(dateStr, lang) end -- add era if era ~= '' then dateStr = translatePhrase(dateStr, '', era, lang, state) end return dateStr end local function twoDatePhrase(date1, date2, state, lang) -- translate a double date phrase local dateStr, case local era='' if state.era1 == state.era2 then -- if both eras are the same than add it only once era = state.era1 state.era1 = '' state.era2 = '' end case = {nil, nil} if i18n.Translations[state.conj] then local rec = langSwitch(i18n.Translations[state.conj], lang) if type(rec)=='table' then case = rec.case end end date1 = oneDatePhrase(date1, state.adj1, state.era1, state.units1, lang, 1, case[1], state) date2 = oneDatePhrase(date2, state.adj2, state.era2, state.units2, lang, 2, case[2], state) dateStr = translatePhrase(date1, date2, state.conj, lang, state) if era ~= '' then dateStr = translatePhrase(dateStr, '', era, lang, state) end return dateStr end local function otherPhrases(date1, date2, operation, era, lang, state) -- translate specialized phrases local dateStr = '' if operation == 'islamic' then if date2=='' then date2 = mw.getCurrentFrame():callParserFunction('#time', 'xmY', date1) end date1 = getISODate(date1, operation, lang, 1, nil) date2 = getISODate(date2, operation, lang, 2, nil) if era == '' then era = 'ad' end dateStr = translatePhrase(date1, '', era, lang, state) .. ' (' .. translatePhrase(date2, '', 'ah', lang, state) .. ')' era = '' elseif operation == 'julian' then if not date2 and date1 then -- Convert from Julian to Gregorian calendar date local JDN = Calendar._date2jdn(date1, 0) if JDN then date2 = date1 -- first date is assumed to be Julian date1 = Calendar._jdn2date(JDN, 1) end end date1 = getISODate(date1, operation, lang, 1, nil) date2 = getISODate(date2, operation, lang, 2, nil) dateStr = translatePhrase(date1, date2, operation, lang, state) dateStr = mw.ustring.gsub(mw.ustring.gsub(dateStr, '%( ', '('), ' %)', ')') -- in case date2 is empty elseif operation == 'turn of the year' or operation == 'turn of the decade' or operation == 'turn of the century' then if operation == 'turn of the decade' then dt=10 else dt=1 end if not date2 or date2=='' then date2=tostring(tonumber(date1)-dt) end if era~='bp' and era~='bc' then date1, date2 = date2, date1 end if operation == 'turn of the year' then date1 = ISOdate(date1, lang, '', '', 1) date2 = ISOdate(date2, lang, '', '', 1) else date1 = formatnum1(date1, lang) date2 = formatnum1(date2, lang) end dateStr = translatePhrase(date1, date2, operation, lang, state) elseif operation == 'year unknown' then dateStr = translatePhrase('', '', operation, lang, state) elseif operation == 'unknown' then dateStr = tostring(mw.message.new( "exif-unknowndate" ):inLanguage( lang )) end -- add era if era ~= '' then dateStr = translatePhrase(dateStr, '', era, lang, state) end return dateStr end local function checkAliases(str1, str2, sType) -- some inputs have many aliases - reconcile them and ensure string is playing a proper role local out = '' if str1 and str1~='' then a = i18n.Synonyms[str1] -- look up synonyms of "str1" if a then out = a[1] else p.Error = string.format('<span style="background-color:red;">Error in [[Module:Complex date]]: %s is not recognized.</span>', str1) end elseif str2 and str2~='' then -- if "str1" of type "sType" is empty than maybe ... a = i18n.Synonyms[str2] -- ..."str2" is of the same type and is not empty if a and a[2]==sType then out = a[1] str2 = '' end end return out, str2 end local function datePrecision(dateStr, units) -- "in this module "Units" is a string like millennium, century, or decade -- "precision" is wikibase compatible date precision number: 6=millennium, 7=century, 8=decade, 9=year, 10=month, 11=day -- based on string or numeric input calculate "Units" and "precision" local dateNum = tonumber(dateStr); if type(units)=='number' then precision = units if precision>11 then precision=11 end -- clip the range of precision values if precision==6 then units='millennium' elseif precision==7 then units='century' elseif precision==8 then units='decade' else units = '' end elseif type(units)=='string' then units = string.lower(units); if units=='millennium' then precision=6 elseif units=='century' then precision=7 elseif units=='decade' then precision=8 else precision=9 end end if units=='' or precision==9 then local sLen = mw.ustring.len(dateStr) if sLen<= 4 then precision=9 elseif sLen== 7 then precision=10 elseif sLen>=10 then precision=11 end units='' end if precision==6 and dateStr.match( dateStr, '%d000' )~=nil then dateStr = tostring(math.floor(tonumber(dateStr)/1000) +1) elseif precision==7 and mw.ustring.match( dateStr, '%d%d00' )~=nil then dateStr = tostring(math.floor(tonumber(dateStr)/100) +1) end return dateStr, units, precision end local function isodate2timestamp(dateStr, precision, era) -- convert date string to timestamps used by Quick Statements local tStamp = nil if era == 'ah' or precision<6 then return nil elseif era ~= '' then eraLUT = {ad='+', bc='-', bp='-' } era = eraLUT[era] else era='+' end -- convert isodate to timestamp used by quick statements if precision>=9 then if string.match(dateStr,"^%d%d%d%d$") then -- if YYYY format tStamp = era .. dateStr .. '-00-00T00:00:00Z/9' elseif string.match(dateStr,"^%d%d%d%d%-%d%d$") then -- if YYYY-MM format tStamp = era .. dateStr .. '-00T00:00:00Z/10' elseif string.match(dateStr,"^%d%d%d%d%-%d%d%-%d%d$") then -- if YYYY-MM-DD format tStamp = era .. dateStr .. 'T00:00:00Z/11' end elseif precision==8 then -- decade tStamp = era .. dateStr .. '-00-00T00:00:00Z/8' elseif precision==7 then -- century local d = tostring(tonumber(dateStr)-1) tStamp = era .. d .. '50-00-00T00:00:00Z/7' elseif precision==6 then local d = tostring(tonumber(dateStr)-1) tStamp = era .. d .. '500-00-00T00:00:00Z/6' end return tStamp end local function oneDateQScode(dateStr, adj, era, precision) -- create QuickStatements string for "one date" dates local outputStr = '' local d = isodate2timestamp(dateStr, precision, era) if not d then return '' end local rLUT = { early='Q40719727' , mid='Q40719748', late='Q40719766', ['1quarter']='Q40690303' , ['2quarter']='Q40719649' , ['3quarter']='Q40719662', ['4quarter']='Q40719674', spring='Q40720559' , summer='Q40720564' , autumn='Q40720568' , winter='Q40720553', firsthalf='Q40719687', secondhalf='Q40719707' } local qLUT = {['from']='P580', ['until']='P582', ['after']='P1319', ['before']='P1326'} local refine = rLUT[adj] local qualitier = qLUT[adj] if adj=='' then outputStr = d elseif adj=='circa' then outputStr = d..",P1480,Q5727902" elseif refine then outputStr = d..",P4241,"..refine elseif precision>7 and qualitier then local century = string.gsub(d, 'Z%/%d+', 'Z/7') outputStr = century ..",".. qualitier ..","..d end return outputStr end local function twoDateQScode(date1, date2, state) -- create QuickStatements string for "two date" dates if state.adj1~='' or state.adj2~='' or state.era1~=state.era2 then return '' end local outputStr = '' local d1 = isodate2timestamp(date1, state.precision1, state.era1) local d2 = isodate2timestamp(date2, state.precision2, state.era2) if (not d1) or (not d2) then return '' end -- find date with lower precision in common to both dates local cd local year1 = string.sub(d1,2,5) local k = 0 for i = 1,10,1 do if string.sub(d1,1,i)==string.sub(d2,1,i) then k = i -- find last matching letter end end if k>=9 then -- same month, since "+YYYY-MM-" is in common cd = isodate2timestamp(string.sub(d1,2,8), 10, state.era1) elseif k>=6 and k<9 then -- same year, since "+YYYY-" is in common cd = isodate2timestamp(year1, 9, state.era1) elseif k==4 then -- same decade(k=4, precision=8), since "+YYY" is in common cd = isodate2timestamp(year1, 8, state.era1) elseif k==3 then -- same century(k=3, precision=7) since "+YY" is in common local d = tostring(math.floor(tonumber(year1)/100) +1) -- convert 1999 -> 20 cd = isodate2timestamp( d, 7, state.era1) elseif k==2 then -- same millennium (k=2, precision=6), since "+Y" is in common local d = tostring(math.floor(tonumber(year1)/1000) +1) -- convert 1999 -> 2 cd = isodate2timestamp( d, 6, state.era1) else return '' end --if not cd then -- return ' <br/>error: ' .. d1.." / " .. d2.." / ".. (cd or '') .." / ".. string.sub(d1,2,5).." / " .. string.sub(d2,2,5).." / " .. tostring(k) --end -- if state.conj=='from-until' then outputStr = cd ..",P580,".. d1 ..",P582,".. d2 elseif state.conj=='between' then outputStr = cd ..",P1319,".. d1 ..",P1326,".. d2 elseif state.conj=='circa2' then outputStr = cd ..",P1319,".. d1 ..",P1326,".. d2 ..",P1480,Q5727902" end return outputStr end -- ================================================== -- === External functions =========================== -- ================================================== function p.Era(frame) -- process inputs local dateStr local args = frame.args if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then args.lang = frame:callParserFunction( "int", "lang" ) -- get user's chosen language end local lang = args['lang'] local dateStr = args['date'] or '' local eraType = string.lower(args['era'] or '') dateStr = ISOdate(dateStr, lang, '', '', 1) if eraType then eraType = checkAliases(eraType ,'','e') dateStr = translatePhrase(dateStr, '', eraType, lang, {}) end return dateStr end function p._complex_date(conj, adj1, date1, units1, era1, adj2, date2, units2, era2, lang, passNr) local Output='' -- process inputs and save date in state array local state = {} state.conj = string.lower(conj or '') state.adj1 = string.lower(adj1 or '') state.adj2 = string.lower(adj2 or '') state.era1 = string.lower(era1 or '') state.era2 = string.lower(era2 or '') state.units1 = string.lower(units1 or '') state.units2 = string.lower(units2 or '') -- if date 1 is missing but date 2 is provided than swap them if date1 == '' and date2 ~= '' then date1 = date2 date2 = '' state = {adj1 = state.adj2, era1 = state.era2, units1 = state.units2, adj2 = '', era2 = '', units2 = '', conj=state.conj, num=1} end if date2 ~= '' then state.nDates = 2 elseif date1 ~= '' then state.nDates = 1 else state.nDates = 0 end -- reconcile alternative names for text inputs local conj = checkAliases(state.conj ,'' ,'j') state.adj1 ,conj = checkAliases(state.adj1 ,conj,'a') state.units1,conj = checkAliases(state.units1,conj,'p') state.era1 ,conj = checkAliases(state.era1 ,conj,'e') state.special,conj = checkAliases('',conj,'c') state.adj2 = checkAliases(state.adj2 ,'','a') state.units2 = checkAliases(state.units2,'','p') state.era2 = checkAliases(state.era2 ,'','e') state.conj = conj state.lang = lang if p.Error~=nil then return nil end -- calculate date precision value date1, state.units1, state.precision1 = datePrecision(date1, state.units1) date2, state.units2, state.precision2 = datePrecision(date2, state.units2) -- Handle special cases -- Some complex phrases can be created out of simpler ones. Therefore on pass # 1 we try to create -- the phrase using complex phrase and if that is not found than on the second pass we try to build -- the phrase out of the simpler ones if passNr==1 then if state.adj1=='circa' and state.nDates == 2 then state.conj = 'circa2' state.adj1 = '' state.adj2 = '' end if state.nDates == 2 and state.adj1=='late' and state.adj2=='early' and state.conj=='and' and state.units1==state.units2 and state.era1==state.era2 then if state.units1=='century' then state.conj='turn of the century' elseif state.units1=='decade' then state.conj='turn of the decade' elseif state.units1=='' then state.conj='turn of the year' end state.adj1 = '' state.adj2 = '' state.units1 = '' state.units2 = '' end end local errorStr = string.format( '\n*conj=%s, adj1=%s, era1=%s, unit1=%s, prec1=%i, adj2=%s, era2=%s, unit2=%s, prec2=%i, special=%s', state.conj, state.adj1, state.era1, state.units1, state.precision1, state.adj2, state.era2, state.units2, state.precision2, state.special) state.adj, state.era, state.units, state.precision = state.adj1, state.era1, state.units1, state.precision1 end -- call specialized functions local QScode = '' if state.special~='' then Output = otherPhrases(date1, date2, state.special, state.era1, lang, state) elseif state.conj~='' then QScode = twoDateQScode(date1, date2, state) Output = twoDatePhrase(date1, date2, state, lang) elseif state.adj1~='' or state.era1~='' or state.units1~='' then Output = oneDatePhrase(date1, state.adj1, state.era1, state.units1, lang, 1, nil, state) QScode = oneDateQScode(date1, state.adj1, state.era1, state.precision1) end return errorStr end -- if there is any wikicode in the string than execute it if mw.ustring.find(Output, '{') then Output = mw.getCurrentFrame():preprocess(Output) end if QScode and #QScode>0 then QScode = ' <div style="display: none;">date QS:P,' .. QScode .. '</div>' end function p.complex_date(frame) -- process inputs local dateStr, Error local args = frame.args if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then args.lang = frame:callParserFunction( "int", "lang" ) -- get user's chosen language end local date1 = args['date1'] or args['2'] or args['date'] or '' local date2 = args['date2'] or args['3'] or '' local conj = args['conj'] or args['1'] or '' local adj1 = args['adj1'] or args['adj'] or '' local adj2 = args['adj2'] or '' local units1 = args['precision1'] or args['precision'] or '' local units2 = args['precision2'] or args['precision'] or '' local era1 = args['era1'] or args['era'] or '' local era2 = args['era2'] or args['era'] or '' local lang = args['lang'] dateStr = p._complex_date(conj, adj1, date1, units1, era1, adj2, date2, units2, era2, lang, 1) if p.Error~=nil then dateStr = p.Error .. '[[Category:Pages using Complex date template with incorrect parameter]]' end return dateStr end return p 6mwo4vti5ysgc642szpltekkodpbj56 803626 803625 2019-01-24T00:48:36Z en>Ferret 0 Undid revision 879882658 by [[Special:Contributions/153.185.54.127|153.185.54.127]] ([[User talk:153.185.54.127|talk]]) Broken every infobox relying on this module. 803626 Scribunto text/plain --[[ __ __ _ _ ____ _ _ _ | \/ | ___ __| |_ _| | ___ _ / ___|___ _ __ ___ _ __ | | _____ __ __| | __ _| |_ ___ | |\/| |/ _ \ / _` | | | | |/ _ (_) | / _ \| '_ ` _ \| '_ \| |/ _ \ \/ / / _` |/ _` | __/ _ \ | | | | (_) | (_| | |_| | | __/_| |__| (_) | | | | | | |_) | | __/> < | (_| | (_| | || __/ |_| |_|\___/ \__,_|\__,_|_|\___(_)\____\___/|_| |_| |_| .__/|_|\___/_/\_\ \__,_|\__,_|\__\___| |_| This module is intended for creation of complex date phrases in variety of languages. Once deployed, please do not modify this code without applying the changes first at Module:Complex date/sandbox and testing at Module:Complex date/sandbox/testcases. Authors and maintainers: * User:Sn1per - first draft of the original version * User:Jarekt - corrections and expansion of the original version ]] -- List of external modules and functions local p = {Error = nil} local i18n = require('Module:i18n/complex date') local ISOdate = require('Module:ISOdate')._ISOdate local Calendar = require('Module:Calendar') -- ================================================== -- === Internal functions =========================== -- ================================================== local function langSwitch(list,lang) local langList = mw.language.getFallbacksFor(lang) table.insert(langList,1,lang) table.insert(langList,math.max(#langList,2),'default') for i,language in ipairs(langList) do if list[language] then return list[language] end end end local function formatnum1(numStr, lang) -- mostly require('Module:Formatnum').formatNum function used to translate a number to use different numeral characters, -- except that it it does not call that function unless the language is on the list "LList" local LList = {bn=1,bpy=1,kn=1,hi=1,mr=1,new=1,pa=1,gu=1,fa=1,glk=1,mzn=1,ur=1,ar=1,ckb=1,ks=1,lo=1,['or']=1,bo=1,['ml-old']=1,mn=1,te=1,th=1} if LList[lang] then -- call only when the language is on the list numStr = require('Module:Formatnum').formatNum(numStr, lang, 1) end return numStr end local function getISODate(datestr, datetype, lang, num, case) -- translate dates in the format YYYY, YYYY-MM, and YYYY-MM-DD if not case and i18n.Translations[datetype] then -- look up the grammatical case needed and call ISOdate module local rec = langSwitch(i18n.Translations[datetype], lang) if type(rec)=='table' then case = rec.case[num] end end return ISOdate(datestr, lang, case, '', 1) end local function translatePhrase(date1, date2, operation, lang, state) -- use tables in Module:i18n/complex date to translate a phrase if not i18n.Translations[operation] then p.Error = string.format('<span style="background-color:red;">Error in [[Module:Complex date]]: input parameter "%s" is not recognized.</span>', operation or 'nil') return '' end local dateStr = langSwitch(i18n.Translations[operation], lang) if type(dateStr)=='table' then dateStr = dateStr[1] end if type(dateStr)=='function' then local success local nDates = i18n.Translations[operation]['nDates'] if nDates==2 then -- 2 date phrase success, dateStr = pcall(dateStr, date1, date2, state) else -- 1 date phrase success, dateStr = pcall(dateStr, date1, state) end end if type(dateStr)=='string' then -- replace parts of the string '$date1' and '$date2' with date1 and date2 strings dateStr = mw.ustring.gsub(dateStr, '$date1', date1) dateStr = mw.ustring.gsub(dateStr, '$date2', date2) else -- Special case of more complex phrases that can be build out of simple phrases -- If complex case is not translated to "lang" than build it out of simpler ones local x = dateStr dateStr = p._complex_date(x.conj, x.adj1, date1, x.units1, x.era1, x.adj2, date2, x.units2, x.era2, lang, 2) end return dateStr end local function oneDatePhrase(dateStr, adj, era, units, lang, num, case, state) -- translate a single date phrase if num==2 then state.adj, state.era, state.units, state.precision = state.adj2, state.era2, state.units2, state.precision2 end -- dateStr can have many forms: ISO date, year or a number for -- decade, century or millennium if units == '' then -- unit is "year", "month", "day" dateStr = getISODate(dateStr, adj, lang, num, case) else -- units is "decade", "century", "millennium'' dateStr = translatePhrase(dateStr, '', units, lang, state) end -- add adjective ("early", "mid", etc.) or preposition ("before", "after", -- "circa", etc.) to the date if adj ~= '' then dateStr = translatePhrase(dateStr, '', adj, lang, state) else -- only era? dateStr = formatnum1(dateStr, lang) end -- add era if era ~= '' then dateStr = translatePhrase(dateStr, '', era, lang, state) end return dateStr end local function twoDatePhrase(date1, date2, state, lang) -- translate a double date phrase local dateStr, case local era='' if state.era1 == state.era2 then -- if both eras are the same than add it only once era = state.era1 state.era1 = '' state.era2 = '' end case = {nil, nil} if i18n.Translations[state.conj] then local rec = langSwitch(i18n.Translations[state.conj], lang) if type(rec)=='table' then case = rec.case end end date1 = oneDatePhrase(date1, state.adj1, state.era1, state.units1, lang, 1, case[1], state) date2 = oneDatePhrase(date2, state.adj2, state.era2, state.units2, lang, 2, case[2], state) dateStr = translatePhrase(date1, date2, state.conj, lang, state) if era ~= '' then dateStr = translatePhrase(dateStr, '', era, lang, state) end return dateStr end local function otherPhrases(date1, date2, operation, era, lang, state) -- translate specialized phrases local dateStr = '' if operation == 'islamic' then if date2=='' then date2 = mw.getCurrentFrame():callParserFunction('#time', 'xmY', date1) end date1 = getISODate(date1, operation, lang, 1, nil) date2 = getISODate(date2, operation, lang, 2, nil) if era == '' then era = 'ad' end dateStr = translatePhrase(date1, '', era, lang, state) .. ' (' .. translatePhrase(date2, '', 'ah', lang, state) .. ')' era = '' elseif operation == 'julian' then if not date2 and date1 then -- Convert from Julian to Gregorian calendar date local JDN = Calendar._date2jdn(date1, 0) if JDN then date2 = date1 -- first date is assumed to be Julian date1 = Calendar._jdn2date(JDN, 1) end end date1 = getISODate(date1, operation, lang, 1, nil) date2 = getISODate(date2, operation, lang, 2, nil) dateStr = translatePhrase(date1, date2, operation, lang, state) dateStr = mw.ustring.gsub(mw.ustring.gsub(dateStr, '%( ', '('), ' %)', ')') -- in case date2 is empty elseif operation == 'turn of the year' or operation == 'turn of the decade' or operation == 'turn of the century' then if operation == 'turn of the decade' then dt=10 else dt=1 end if not date2 or date2=='' then date2=tostring(tonumber(date1)-dt) end if era~='bp' and era~='bc' then date1, date2 = date2, date1 end if operation == 'turn of the year' then date1 = ISOdate(date1, lang, '', '', 1) date2 = ISOdate(date2, lang, '', '', 1) else date1 = formatnum1(date1, lang) date2 = formatnum1(date2, lang) end dateStr = translatePhrase(date1, date2, operation, lang, state) elseif operation == 'year unknown' then dateStr = translatePhrase('', '', operation, lang, state) elseif operation == 'unknown' then dateStr = tostring(mw.message.new( "exif-unknowndate" ):inLanguage( lang )) end -- add era if era ~= '' then dateStr = translatePhrase(dateStr, '', era, lang, state) end return dateStr end local function checkAliases(str1, str2, sType) -- some inputs have many aliases - reconcile them and ensure string is playing a proper role local out = '' if str1 and str1~='' then a = i18n.Synonyms[str1] -- look up synonyms of "str1" if a then out = a[1] else p.Error = string.format('<span style="background-color:red;">Error in [[Module:Complex date]]: %s is not recognized.</span>', str1) end elseif str2 and str2~='' then -- if "str1" of type "sType" is empty than maybe ... a = i18n.Synonyms[str2] -- ..."str2" is of the same type and is not empty if a and a[2]==sType then out = a[1] str2 = '' end end return out, str2 end local function datePrecision(dateStr, units) -- "in this module "Units" is a string like millennium, century, or decade -- "precision" is wikibase compatible date precision number: 6=millennium, 7=century, 8=decade, 9=year, 10=month, 11=day -- based on string or numeric input calculate "Units" and "precision" local dateNum = tonumber(dateStr); if type(units)=='number' then precision = units if precision>11 then precision=11 end -- clip the range of precision values if precision==6 then units='millennium' elseif precision==7 then units='century' elseif precision==8 then units='decade' else units = '' end elseif type(units)=='string' then units = string.lower(units); if units=='millennium' then precision=6 elseif units=='century' then precision=7 elseif units=='decade' then precision=8 else precision=9 end end if units=='' or precision==9 then local sLen = mw.ustring.len(dateStr) if sLen<= 4 then precision=9 elseif sLen== 7 then precision=10 elseif sLen>=10 then precision=11 end units='' end if precision==6 and dateStr.match( dateStr, '%d000' )~=nil then dateStr = tostring(math.floor(tonumber(dateStr)/1000) +1) elseif precision==7 and mw.ustring.match( dateStr, '%d%d00' )~=nil then dateStr = tostring(math.floor(tonumber(dateStr)/100) +1) end return dateStr, units, precision end local function isodate2timestamp(dateStr, precision, era) -- convert date string to timestamps used by Quick Statements local tStamp = nil if era == 'ah' or precision<6 then return nil elseif era ~= '' then eraLUT = {ad='+', bc='-', bp='-' } era = eraLUT[era] else era='+' end -- convert isodate to timestamp used by quick statements if precision>=9 then if string.match(dateStr,"^%d%d%d%d$") then -- if YYYY format tStamp = era .. dateStr .. '-00-00T00:00:00Z/9' elseif string.match(dateStr,"^%d%d%d%d%-%d%d$") then -- if YYYY-MM format tStamp = era .. dateStr .. '-00T00:00:00Z/10' elseif string.match(dateStr,"^%d%d%d%d%-%d%d%-%d%d$") then -- if YYYY-MM-DD format tStamp = era .. dateStr .. 'T00:00:00Z/11' end elseif precision==8 then -- decade tStamp = era .. dateStr .. '-00-00T00:00:00Z/8' elseif precision==7 then -- century local d = tostring(tonumber(dateStr)-1) tStamp = era .. d .. '50-00-00T00:00:00Z/7' elseif precision==6 then local d = tostring(tonumber(dateStr)-1) tStamp = era .. d .. '500-00-00T00:00:00Z/6' end return tStamp end local function oneDateQScode(dateStr, adj, era, precision) -- create QuickStatements string for "one date" dates local outputStr = '' local d = isodate2timestamp(dateStr, precision, era) if not d then return '' end local rLUT = { early='Q40719727' , mid='Q40719748', late='Q40719766', ['1quarter']='Q40690303' , ['2quarter']='Q40719649' , ['3quarter']='Q40719662', ['4quarter']='Q40719674', spring='Q40720559' , summer='Q40720564' , autumn='Q40720568' , winter='Q40720553', firsthalf='Q40719687', secondhalf='Q40719707' } local qLUT = {['from']='P580', ['until']='P582', ['after']='P1319', ['before']='P1326'} local refine = rLUT[adj] local qualitier = qLUT[adj] if adj=='' then outputStr = d elseif adj=='circa' then outputStr = d..",P1480,Q5727902" elseif refine then outputStr = d..",P4241,"..refine elseif precision>7 and qualitier then local century = string.gsub(d, 'Z%/%d+', 'Z/7') outputStr = century ..",".. qualitier ..","..d end return outputStr end local function twoDateQScode(date1, date2, state) -- create QuickStatements string for "two date" dates if state.adj1~='' or state.adj2~='' or state.era1~=state.era2 then return '' end local outputStr = '' local d1 = isodate2timestamp(date1, state.precision1, state.era1) local d2 = isodate2timestamp(date2, state.precision2, state.era2) if (not d1) or (not d2) then return '' end -- find date with lower precision in common to both dates local cd local year1 = string.sub(d1,2,5) local k = 0 for i = 1,10,1 do if string.sub(d1,1,i)==string.sub(d2,1,i) then k = i -- find last matching letter end end if k>=9 then -- same month, since "+YYYY-MM-" is in common cd = isodate2timestamp(string.sub(d1,2,8), 10, state.era1) elseif k>=6 and k<9 then -- same year, since "+YYYY-" is in common cd = isodate2timestamp(year1, 9, state.era1) elseif k==4 then -- same decade(k=4, precision=8), since "+YYY" is in common cd = isodate2timestamp(year1, 8, state.era1) elseif k==3 then -- same century(k=3, precision=7) since "+YY" is in common local d = tostring(math.floor(tonumber(year1)/100) +1) -- convert 1999 -> 20 cd = isodate2timestamp( d, 7, state.era1) elseif k==2 then -- same millennium (k=2, precision=6), since "+Y" is in common local d = tostring(math.floor(tonumber(year1)/1000) +1) -- convert 1999 -> 2 cd = isodate2timestamp( d, 6, state.era1) else return '' end --if not cd then -- return ' <br/>error: ' .. d1.." / " .. d2.." / ".. (cd or '') .." / ".. string.sub(d1,2,5).." / " .. string.sub(d2,2,5).." / " .. tostring(k) --end -- if state.conj=='from-until' then outputStr = cd ..",P580,".. d1 ..",P582,".. d2 elseif state.conj=='between' then outputStr = cd ..",P1319,".. d1 ..",P1326,".. d2 elseif state.conj=='circa2' then outputStr = cd ..",P1319,".. d1 ..",P1326,".. d2 ..",P1480,Q5727902" end return outputStr end -- ================================================== -- === External functions =========================== -- ================================================== function p.Era(frame) -- process inputs local dateStr local args = frame.args if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then args.lang = frame:callParserFunction( "int", "lang" ) -- get user's chosen language end local lang = args['lang'] local dateStr = args['date'] or '' local eraType = string.lower(args['era'] or '') dateStr = ISOdate(dateStr, lang, '', '', 1) if eraType then eraType = checkAliases(eraType ,'','e') dateStr = translatePhrase(dateStr, '', eraType, lang, {}) end return dateStr end function p._complex_date(conj, adj1, date1, units1, era1, adj2, date2, units2, era2, lang, passNr) local Output='' -- process inputs and save date in state array local state = {} state.conj = string.lower(conj or '') state.adj1 = string.lower(adj1 or '') state.adj2 = string.lower(adj2 or '') state.era1 = string.lower(era1 or '') state.era2 = string.lower(era2 or '') state.units1 = string.lower(units1 or '') state.units2 = string.lower(units2 or '') -- if date 1 is missing but date 2 is provided than swap them if date1 == '' and date2 ~= '' then date1 = date2 date2 = '' state = {adj1 = state.adj2, era1 = state.era2, units1 = state.units2, adj2 = '', era2 = '', units2 = '', conj=state.conj, num=1} end if date2 ~= '' then state.nDates = 2 elseif date1 ~= '' then state.nDates = 1 else state.nDates = 0 end -- reconcile alternative names for text inputs local conj = checkAliases(state.conj ,'' ,'j') state.adj1 ,conj = checkAliases(state.adj1 ,conj,'a') state.units1,conj = checkAliases(state.units1,conj,'p') state.era1 ,conj = checkAliases(state.era1 ,conj,'e') state.special,conj = checkAliases('',conj,'c') state.adj2 = checkAliases(state.adj2 ,'','a') state.units2 = checkAliases(state.units2,'','p') state.era2 = checkAliases(state.era2 ,'','e') state.conj = conj state.lang = lang if p.Error~=nil then return nil end -- calculate date precision value date1, state.units1, state.precision1 = datePrecision(date1, state.units1) date2, state.units2, state.precision2 = datePrecision(date2, state.units2) -- Handle special cases -- Some complex phrases can be created out of simpler ones. Therefore on pass # 1 we try to create -- the phrase using complex phrase and if that is not found than on the second pass we try to build -- the phrase out of the simpler ones if passNr==1 then if state.adj1=='circa' and state.nDates == 2 then state.conj = 'circa2' state.adj1 = '' state.adj2 = '' end if state.nDates == 2 and state.adj1=='late' and state.adj2=='early' and state.conj=='and' and state.units1==state.units2 and state.era1==state.era2 then if state.units1=='century' then state.conj='turn of the century' elseif state.units1=='decade' then state.conj='turn of the decade' elseif state.units1=='' then state.conj='turn of the year' end state.adj1 = '' state.adj2 = '' state.units1 = '' state.units2 = '' end end local errorStr = string.format( '\n*conj=%s, adj1=%s, era1=%s, unit1=%s, prec1=%i, adj2=%s, era2=%s, unit2=%s, prec2=%i, special=%s', state.conj, state.adj1, state.era1, state.units1, state.precision1, state.adj2, state.era2, state.units2, state.precision2, state.special) state.adj, state.era, state.units, state.precision = state.adj1, state.era1, state.units1, state.precision1 -- call specialized functions local QScode = '' if state.special~='' then Output = otherPhrases(date1, date2, state.special, state.era1, lang, state) elseif state.conj~='' then QScode = twoDateQScode(date1, date2, state) Output = twoDatePhrase(date1, date2, state, lang) elseif state.adj1~='' or state.era1~='' or state.units1~='' then Output = oneDatePhrase(date1, state.adj1, state.era1, state.units1, lang, 1, nil, state) QScode = oneDateQScode(date1, state.adj1, state.era1, state.precision1) elseif date1~='' then Output = ISOdate(date1, lang, '', 'dtstart', '100-999') end if p.Error~=nil then return errorStr end -- if there is any wikicode in the string than execute it if mw.ustring.find(Output, '{') then Output = mw.getCurrentFrame():preprocess(Output) end if QScode and #QScode>0 then QScode = ' <div style="display: none;">date QS:P,' .. QScode .. '</div>' end return Output .. QScode end function p.complex_date(frame) -- process inputs local dateStr, Error local args = frame.args if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then args.lang = frame:callParserFunction( "int", "lang" ) -- get user's chosen language end local date1 = args['date1'] or args['2'] or args['date'] or '' local date2 = args['date2'] or args['3'] or '' local conj = args['conj'] or args['1'] or '' local adj1 = args['adj1'] or args['adj'] or '' local adj2 = args['adj2'] or '' local units1 = args['precision1'] or args['precision'] or '' local units2 = args['precision2'] or args['precision'] or '' local era1 = args['era1'] or args['era'] or '' local era2 = args['era2'] or args['era'] or '' local lang = args['lang'] dateStr = p._complex_date(conj, adj1, date1, units1, era1, adj2, date2, units2, era2, lang, 1) if p.Error~=nil then dateStr = p.Error .. '[[Category:Pages using Complex date template with incorrect parameter]]' end return dateStr end return p jrhelia769tliicwgtvdi07i8r3cz5m 803627 803626 2019-01-24T00:48:49Z en>Ferret 0 Protected "[[Module:Complex date]]": [[WP:High-risk templates|Highly visible template]] ([Edit=Require template editor access] (indefinite) [Move=Require template editor access] (indefinite)) 803626 Scribunto text/plain --[[ __ __ _ _ ____ _ _ _ | \/ | ___ __| |_ _| | ___ _ / ___|___ _ __ ___ _ __ | | _____ __ __| | __ _| |_ ___ | |\/| |/ _ \ / _` | | | | |/ _ (_) | / _ \| '_ ` _ \| '_ \| |/ _ \ \/ / / _` |/ _` | __/ _ \ | | | | (_) | (_| | |_| | | __/_| |__| (_) | | | | | | |_) | | __/> < | (_| | (_| | || __/ |_| |_|\___/ \__,_|\__,_|_|\___(_)\____\___/|_| |_| |_| .__/|_|\___/_/\_\ \__,_|\__,_|\__\___| |_| This module is intended for creation of complex date phrases in variety of languages. Once deployed, please do not modify this code without applying the changes first at Module:Complex date/sandbox and testing at Module:Complex date/sandbox/testcases. Authors and maintainers: * User:Sn1per - first draft of the original version * User:Jarekt - corrections and expansion of the original version ]] -- List of external modules and functions local p = {Error = nil} local i18n = require('Module:i18n/complex date') local ISOdate = require('Module:ISOdate')._ISOdate local Calendar = require('Module:Calendar') -- ================================================== -- === Internal functions =========================== -- ================================================== local function langSwitch(list,lang) local langList = mw.language.getFallbacksFor(lang) table.insert(langList,1,lang) table.insert(langList,math.max(#langList,2),'default') for i,language in ipairs(langList) do if list[language] then return list[language] end end end local function formatnum1(numStr, lang) -- mostly require('Module:Formatnum').formatNum function used to translate a number to use different numeral characters, -- except that it it does not call that function unless the language is on the list "LList" local LList = {bn=1,bpy=1,kn=1,hi=1,mr=1,new=1,pa=1,gu=1,fa=1,glk=1,mzn=1,ur=1,ar=1,ckb=1,ks=1,lo=1,['or']=1,bo=1,['ml-old']=1,mn=1,te=1,th=1} if LList[lang] then -- call only when the language is on the list numStr = require('Module:Formatnum').formatNum(numStr, lang, 1) end return numStr end local function getISODate(datestr, datetype, lang, num, case) -- translate dates in the format YYYY, YYYY-MM, and YYYY-MM-DD if not case and i18n.Translations[datetype] then -- look up the grammatical case needed and call ISOdate module local rec = langSwitch(i18n.Translations[datetype], lang) if type(rec)=='table' then case = rec.case[num] end end return ISOdate(datestr, lang, case, '', 1) end local function translatePhrase(date1, date2, operation, lang, state) -- use tables in Module:i18n/complex date to translate a phrase if not i18n.Translations[operation] then p.Error = string.format('<span style="background-color:red;">Error in [[Module:Complex date]]: input parameter "%s" is not recognized.</span>', operation or 'nil') return '' end local dateStr = langSwitch(i18n.Translations[operation], lang) if type(dateStr)=='table' then dateStr = dateStr[1] end if type(dateStr)=='function' then local success local nDates = i18n.Translations[operation]['nDates'] if nDates==2 then -- 2 date phrase success, dateStr = pcall(dateStr, date1, date2, state) else -- 1 date phrase success, dateStr = pcall(dateStr, date1, state) end end if type(dateStr)=='string' then -- replace parts of the string '$date1' and '$date2' with date1 and date2 strings dateStr = mw.ustring.gsub(dateStr, '$date1', date1) dateStr = mw.ustring.gsub(dateStr, '$date2', date2) else -- Special case of more complex phrases that can be build out of simple phrases -- If complex case is not translated to "lang" than build it out of simpler ones local x = dateStr dateStr = p._complex_date(x.conj, x.adj1, date1, x.units1, x.era1, x.adj2, date2, x.units2, x.era2, lang, 2) end return dateStr end local function oneDatePhrase(dateStr, adj, era, units, lang, num, case, state) -- translate a single date phrase if num==2 then state.adj, state.era, state.units, state.precision = state.adj2, state.era2, state.units2, state.precision2 end -- dateStr can have many forms: ISO date, year or a number for -- decade, century or millennium if units == '' then -- unit is "year", "month", "day" dateStr = getISODate(dateStr, adj, lang, num, case) else -- units is "decade", "century", "millennium'' dateStr = translatePhrase(dateStr, '', units, lang, state) end -- add adjective ("early", "mid", etc.) or preposition ("before", "after", -- "circa", etc.) to the date if adj ~= '' then dateStr = translatePhrase(dateStr, '', adj, lang, state) else -- only era? dateStr = formatnum1(dateStr, lang) end -- add era if era ~= '' then dateStr = translatePhrase(dateStr, '', era, lang, state) end return dateStr end local function twoDatePhrase(date1, date2, state, lang) -- translate a double date phrase local dateStr, case local era='' if state.era1 == state.era2 then -- if both eras are the same than add it only once era = state.era1 state.era1 = '' state.era2 = '' end case = {nil, nil} if i18n.Translations[state.conj] then local rec = langSwitch(i18n.Translations[state.conj], lang) if type(rec)=='table' then case = rec.case end end date1 = oneDatePhrase(date1, state.adj1, state.era1, state.units1, lang, 1, case[1], state) date2 = oneDatePhrase(date2, state.adj2, state.era2, state.units2, lang, 2, case[2], state) dateStr = translatePhrase(date1, date2, state.conj, lang, state) if era ~= '' then dateStr = translatePhrase(dateStr, '', era, lang, state) end return dateStr end local function otherPhrases(date1, date2, operation, era, lang, state) -- translate specialized phrases local dateStr = '' if operation == 'islamic' then if date2=='' then date2 = mw.getCurrentFrame():callParserFunction('#time', 'xmY', date1) end date1 = getISODate(date1, operation, lang, 1, nil) date2 = getISODate(date2, operation, lang, 2, nil) if era == '' then era = 'ad' end dateStr = translatePhrase(date1, '', era, lang, state) .. ' (' .. translatePhrase(date2, '', 'ah', lang, state) .. ')' era = '' elseif operation == 'julian' then if not date2 and date1 then -- Convert from Julian to Gregorian calendar date local JDN = Calendar._date2jdn(date1, 0) if JDN then date2 = date1 -- first date is assumed to be Julian date1 = Calendar._jdn2date(JDN, 1) end end date1 = getISODate(date1, operation, lang, 1, nil) date2 = getISODate(date2, operation, lang, 2, nil) dateStr = translatePhrase(date1, date2, operation, lang, state) dateStr = mw.ustring.gsub(mw.ustring.gsub(dateStr, '%( ', '('), ' %)', ')') -- in case date2 is empty elseif operation == 'turn of the year' or operation == 'turn of the decade' or operation == 'turn of the century' then if operation == 'turn of the decade' then dt=10 else dt=1 end if not date2 or date2=='' then date2=tostring(tonumber(date1)-dt) end if era~='bp' and era~='bc' then date1, date2 = date2, date1 end if operation == 'turn of the year' then date1 = ISOdate(date1, lang, '', '', 1) date2 = ISOdate(date2, lang, '', '', 1) else date1 = formatnum1(date1, lang) date2 = formatnum1(date2, lang) end dateStr = translatePhrase(date1, date2, operation, lang, state) elseif operation == 'year unknown' then dateStr = translatePhrase('', '', operation, lang, state) elseif operation == 'unknown' then dateStr = tostring(mw.message.new( "exif-unknowndate" ):inLanguage( lang )) end -- add era if era ~= '' then dateStr = translatePhrase(dateStr, '', era, lang, state) end return dateStr end local function checkAliases(str1, str2, sType) -- some inputs have many aliases - reconcile them and ensure string is playing a proper role local out = '' if str1 and str1~='' then a = i18n.Synonyms[str1] -- look up synonyms of "str1" if a then out = a[1] else p.Error = string.format('<span style="background-color:red;">Error in [[Module:Complex date]]: %s is not recognized.</span>', str1) end elseif str2 and str2~='' then -- if "str1" of type "sType" is empty than maybe ... a = i18n.Synonyms[str2] -- ..."str2" is of the same type and is not empty if a and a[2]==sType then out = a[1] str2 = '' end end return out, str2 end local function datePrecision(dateStr, units) -- "in this module "Units" is a string like millennium, century, or decade -- "precision" is wikibase compatible date precision number: 6=millennium, 7=century, 8=decade, 9=year, 10=month, 11=day -- based on string or numeric input calculate "Units" and "precision" local dateNum = tonumber(dateStr); if type(units)=='number' then precision = units if precision>11 then precision=11 end -- clip the range of precision values if precision==6 then units='millennium' elseif precision==7 then units='century' elseif precision==8 then units='decade' else units = '' end elseif type(units)=='string' then units = string.lower(units); if units=='millennium' then precision=6 elseif units=='century' then precision=7 elseif units=='decade' then precision=8 else precision=9 end end if units=='' or precision==9 then local sLen = mw.ustring.len(dateStr) if sLen<= 4 then precision=9 elseif sLen== 7 then precision=10 elseif sLen>=10 then precision=11 end units='' end if precision==6 and dateStr.match( dateStr, '%d000' )~=nil then dateStr = tostring(math.floor(tonumber(dateStr)/1000) +1) elseif precision==7 and mw.ustring.match( dateStr, '%d%d00' )~=nil then dateStr = tostring(math.floor(tonumber(dateStr)/100) +1) end return dateStr, units, precision end local function isodate2timestamp(dateStr, precision, era) -- convert date string to timestamps used by Quick Statements local tStamp = nil if era == 'ah' or precision<6 then return nil elseif era ~= '' then eraLUT = {ad='+', bc='-', bp='-' } era = eraLUT[era] else era='+' end -- convert isodate to timestamp used by quick statements if precision>=9 then if string.match(dateStr,"^%d%d%d%d$") then -- if YYYY format tStamp = era .. dateStr .. '-00-00T00:00:00Z/9' elseif string.match(dateStr,"^%d%d%d%d%-%d%d$") then -- if YYYY-MM format tStamp = era .. dateStr .. '-00T00:00:00Z/10' elseif string.match(dateStr,"^%d%d%d%d%-%d%d%-%d%d$") then -- if YYYY-MM-DD format tStamp = era .. dateStr .. 'T00:00:00Z/11' end elseif precision==8 then -- decade tStamp = era .. dateStr .. '-00-00T00:00:00Z/8' elseif precision==7 then -- century local d = tostring(tonumber(dateStr)-1) tStamp = era .. d .. '50-00-00T00:00:00Z/7' elseif precision==6 then local d = tostring(tonumber(dateStr)-1) tStamp = era .. d .. '500-00-00T00:00:00Z/6' end return tStamp end local function oneDateQScode(dateStr, adj, era, precision) -- create QuickStatements string for "one date" dates local outputStr = '' local d = isodate2timestamp(dateStr, precision, era) if not d then return '' end local rLUT = { early='Q40719727' , mid='Q40719748', late='Q40719766', ['1quarter']='Q40690303' , ['2quarter']='Q40719649' , ['3quarter']='Q40719662', ['4quarter']='Q40719674', spring='Q40720559' , summer='Q40720564' , autumn='Q40720568' , winter='Q40720553', firsthalf='Q40719687', secondhalf='Q40719707' } local qLUT = {['from']='P580', ['until']='P582', ['after']='P1319', ['before']='P1326'} local refine = rLUT[adj] local qualitier = qLUT[adj] if adj=='' then outputStr = d elseif adj=='circa' then outputStr = d..",P1480,Q5727902" elseif refine then outputStr = d..",P4241,"..refine elseif precision>7 and qualitier then local century = string.gsub(d, 'Z%/%d+', 'Z/7') outputStr = century ..",".. qualitier ..","..d end return outputStr end local function twoDateQScode(date1, date2, state) -- create QuickStatements string for "two date" dates if state.adj1~='' or state.adj2~='' or state.era1~=state.era2 then return '' end local outputStr = '' local d1 = isodate2timestamp(date1, state.precision1, state.era1) local d2 = isodate2timestamp(date2, state.precision2, state.era2) if (not d1) or (not d2) then return '' end -- find date with lower precision in common to both dates local cd local year1 = string.sub(d1,2,5) local k = 0 for i = 1,10,1 do if string.sub(d1,1,i)==string.sub(d2,1,i) then k = i -- find last matching letter end end if k>=9 then -- same month, since "+YYYY-MM-" is in common cd = isodate2timestamp(string.sub(d1,2,8), 10, state.era1) elseif k>=6 and k<9 then -- same year, since "+YYYY-" is in common cd = isodate2timestamp(year1, 9, state.era1) elseif k==4 then -- same decade(k=4, precision=8), since "+YYY" is in common cd = isodate2timestamp(year1, 8, state.era1) elseif k==3 then -- same century(k=3, precision=7) since "+YY" is in common local d = tostring(math.floor(tonumber(year1)/100) +1) -- convert 1999 -> 20 cd = isodate2timestamp( d, 7, state.era1) elseif k==2 then -- same millennium (k=2, precision=6), since "+Y" is in common local d = tostring(math.floor(tonumber(year1)/1000) +1) -- convert 1999 -> 2 cd = isodate2timestamp( d, 6, state.era1) else return '' end --if not cd then -- return ' <br/>error: ' .. d1.." / " .. d2.." / ".. (cd or '') .." / ".. string.sub(d1,2,5).." / " .. string.sub(d2,2,5).." / " .. tostring(k) --end -- if state.conj=='from-until' then outputStr = cd ..",P580,".. d1 ..",P582,".. d2 elseif state.conj=='between' then outputStr = cd ..",P1319,".. d1 ..",P1326,".. d2 elseif state.conj=='circa2' then outputStr = cd ..",P1319,".. d1 ..",P1326,".. d2 ..",P1480,Q5727902" end return outputStr end -- ================================================== -- === External functions =========================== -- ================================================== function p.Era(frame) -- process inputs local dateStr local args = frame.args if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then args.lang = frame:callParserFunction( "int", "lang" ) -- get user's chosen language end local lang = args['lang'] local dateStr = args['date'] or '' local eraType = string.lower(args['era'] or '') dateStr = ISOdate(dateStr, lang, '', '', 1) if eraType then eraType = checkAliases(eraType ,'','e') dateStr = translatePhrase(dateStr, '', eraType, lang, {}) end return dateStr end function p._complex_date(conj, adj1, date1, units1, era1, adj2, date2, units2, era2, lang, passNr) local Output='' -- process inputs and save date in state array local state = {} state.conj = string.lower(conj or '') state.adj1 = string.lower(adj1 or '') state.adj2 = string.lower(adj2 or '') state.era1 = string.lower(era1 or '') state.era2 = string.lower(era2 or '') state.units1 = string.lower(units1 or '') state.units2 = string.lower(units2 or '') -- if date 1 is missing but date 2 is provided than swap them if date1 == '' and date2 ~= '' then date1 = date2 date2 = '' state = {adj1 = state.adj2, era1 = state.era2, units1 = state.units2, adj2 = '', era2 = '', units2 = '', conj=state.conj, num=1} end if date2 ~= '' then state.nDates = 2 elseif date1 ~= '' then state.nDates = 1 else state.nDates = 0 end -- reconcile alternative names for text inputs local conj = checkAliases(state.conj ,'' ,'j') state.adj1 ,conj = checkAliases(state.adj1 ,conj,'a') state.units1,conj = checkAliases(state.units1,conj,'p') state.era1 ,conj = checkAliases(state.era1 ,conj,'e') state.special,conj = checkAliases('',conj,'c') state.adj2 = checkAliases(state.adj2 ,'','a') state.units2 = checkAliases(state.units2,'','p') state.era2 = checkAliases(state.era2 ,'','e') state.conj = conj state.lang = lang if p.Error~=nil then return nil end -- calculate date precision value date1, state.units1, state.precision1 = datePrecision(date1, state.units1) date2, state.units2, state.precision2 = datePrecision(date2, state.units2) -- Handle special cases -- Some complex phrases can be created out of simpler ones. Therefore on pass # 1 we try to create -- the phrase using complex phrase and if that is not found than on the second pass we try to build -- the phrase out of the simpler ones if passNr==1 then if state.adj1=='circa' and state.nDates == 2 then state.conj = 'circa2' state.adj1 = '' state.adj2 = '' end if state.nDates == 2 and state.adj1=='late' and state.adj2=='early' and state.conj=='and' and state.units1==state.units2 and state.era1==state.era2 then if state.units1=='century' then state.conj='turn of the century' elseif state.units1=='decade' then state.conj='turn of the decade' elseif state.units1=='' then state.conj='turn of the year' end state.adj1 = '' state.adj2 = '' state.units1 = '' state.units2 = '' end end local errorStr = string.format( '\n*conj=%s, adj1=%s, era1=%s, unit1=%s, prec1=%i, adj2=%s, era2=%s, unit2=%s, prec2=%i, special=%s', state.conj, state.adj1, state.era1, state.units1, state.precision1, state.adj2, state.era2, state.units2, state.precision2, state.special) state.adj, state.era, state.units, state.precision = state.adj1, state.era1, state.units1, state.precision1 -- call specialized functions local QScode = '' if state.special~='' then Output = otherPhrases(date1, date2, state.special, state.era1, lang, state) elseif state.conj~='' then QScode = twoDateQScode(date1, date2, state) Output = twoDatePhrase(date1, date2, state, lang) elseif state.adj1~='' or state.era1~='' or state.units1~='' then Output = oneDatePhrase(date1, state.adj1, state.era1, state.units1, lang, 1, nil, state) QScode = oneDateQScode(date1, state.adj1, state.era1, state.precision1) elseif date1~='' then Output = ISOdate(date1, lang, '', 'dtstart', '100-999') end if p.Error~=nil then return errorStr end -- if there is any wikicode in the string than execute it if mw.ustring.find(Output, '{') then Output = mw.getCurrentFrame():preprocess(Output) end if QScode and #QScode>0 then QScode = ' <div style="display: none;">date QS:P,' .. QScode .. '</div>' end return Output .. QScode end function p.complex_date(frame) -- process inputs local dateStr, Error local args = frame.args if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then args.lang = frame:callParserFunction( "int", "lang" ) -- get user's chosen language end local date1 = args['date1'] or args['2'] or args['date'] or '' local date2 = args['date2'] or args['3'] or '' local conj = args['conj'] or args['1'] or '' local adj1 = args['adj1'] or args['adj'] or '' local adj2 = args['adj2'] or '' local units1 = args['precision1'] or args['precision'] or '' local units2 = args['precision2'] or args['precision'] or '' local era1 = args['era1'] or args['era'] or '' local era2 = args['era2'] or args['era'] or '' local lang = args['lang'] dateStr = p._complex_date(conj, adj1, date1, units1, era1, adj2, date2, units2, era2, lang, 1) if p.Error~=nil then dateStr = p.Error .. '[[Category:Pages using Complex date template with incorrect parameter]]' end return dateStr end return p jrhelia769tliicwgtvdi07i8r3cz5m 803628 803627 2019-02-21T18:49:00Z en>Pppery 0 Lazy load [[Module:Calendar]], not actually needed by this module in the way it is used through [[Module:WikidataIB]] 803628 Scribunto text/plain --[[ __ __ _ _ ____ _ _ _ | \/ | ___ __| |_ _| | ___ _ / ___|___ _ __ ___ _ __ | | _____ __ __| | __ _| |_ ___ | |\/| |/ _ \ / _` | | | | |/ _ (_) | / _ \| '_ ` _ \| '_ \| |/ _ \ \/ / / _` |/ _` | __/ _ \ | | | | (_) | (_| | |_| | | __/_| |__| (_) | | | | | | |_) | | __/> < | (_| | (_| | || __/ |_| |_|\___/ \__,_|\__,_|_|\___(_)\____\___/|_| |_| |_| .__/|_|\___/_/\_\ \__,_|\__,_|\__\___| |_| This module is intended for creation of complex date phrases in variety of languages. Once deployed, please do not modify this code without applying the changes first at Module:Complex date/sandbox and testing at Module:Complex date/sandbox/testcases. Authors and maintainers: * User:Sn1per - first draft of the original version * User:Jarekt - corrections and expansion of the original version ]] -- List of external modules and functions local p = {Error = nil} local i18n = require('Module:i18n/complex date') local ISOdate = require('Module:ISOdate')._ISOdate local Calendar -- loaded lazily -- ================================================== -- === Internal functions =========================== -- ================================================== local function langSwitch(list,lang) local langList = mw.language.getFallbacksFor(lang) table.insert(langList,1,lang) table.insert(langList,math.max(#langList,2),'default') for i,language in ipairs(langList) do if list[language] then return list[language] end end end local function formatnum1(numStr, lang) -- mostly require('Module:Formatnum').formatNum function used to translate a number to use different numeral characters, -- except that it it does not call that function unless the language is on the list "LList" local LList = {bn=1,bpy=1,kn=1,hi=1,mr=1,new=1,pa=1,gu=1,fa=1,glk=1,mzn=1,ur=1,ar=1,ckb=1,ks=1,lo=1,['or']=1,bo=1,['ml-old']=1,mn=1,te=1,th=1} if LList[lang] then -- call only when the language is on the list numStr = require('Module:Formatnum').formatNum(numStr, lang, 1) end return numStr end local function getISODate(datestr, datetype, lang, num, case) -- translate dates in the format YYYY, YYYY-MM, and YYYY-MM-DD if not case and i18n.Translations[datetype] then -- look up the grammatical case needed and call ISOdate module local rec = langSwitch(i18n.Translations[datetype], lang) if type(rec)=='table' then case = rec.case[num] end end return ISOdate(datestr, lang, case, '', 1) end local function translatePhrase(date1, date2, operation, lang, state) -- use tables in Module:i18n/complex date to translate a phrase if not i18n.Translations[operation] then p.Error = string.format('<span style="background-color:red;">Error in [[Module:Complex date]]: input parameter "%s" is not recognized.</span>', operation or 'nil') return '' end local dateStr = langSwitch(i18n.Translations[operation], lang) if type(dateStr)=='table' then dateStr = dateStr[1] end if type(dateStr)=='function' then local success local nDates = i18n.Translations[operation]['nDates'] if nDates==2 then -- 2 date phrase success, dateStr = pcall(dateStr, date1, date2, state) else -- 1 date phrase success, dateStr = pcall(dateStr, date1, state) end end if type(dateStr)=='string' then -- replace parts of the string '$date1' and '$date2' with date1 and date2 strings dateStr = mw.ustring.gsub(dateStr, '$date1', date1) dateStr = mw.ustring.gsub(dateStr, '$date2', date2) else -- Special case of more complex phrases that can be build out of simple phrases -- If complex case is not translated to "lang" than build it out of simpler ones local x = dateStr dateStr = p._complex_date(x.conj, x.adj1, date1, x.units1, x.era1, x.adj2, date2, x.units2, x.era2, lang, 2) end return dateStr end local function oneDatePhrase(dateStr, adj, era, units, lang, num, case, state) -- translate a single date phrase if num==2 then state.adj, state.era, state.units, state.precision = state.adj2, state.era2, state.units2, state.precision2 end -- dateStr can have many forms: ISO date, year or a number for -- decade, century or millennium if units == '' then -- unit is "year", "month", "day" dateStr = getISODate(dateStr, adj, lang, num, case) else -- units is "decade", "century", "millennium'' dateStr = translatePhrase(dateStr, '', units, lang, state) end -- add adjective ("early", "mid", etc.) or preposition ("before", "after", -- "circa", etc.) to the date if adj ~= '' then dateStr = translatePhrase(dateStr, '', adj, lang, state) else -- only era? dateStr = formatnum1(dateStr, lang) end -- add era if era ~= '' then dateStr = translatePhrase(dateStr, '', era, lang, state) end return dateStr end local function twoDatePhrase(date1, date2, state, lang) -- translate a double date phrase local dateStr, case local era='' if state.era1 == state.era2 then -- if both eras are the same than add it only once era = state.era1 state.era1 = '' state.era2 = '' end case = {nil, nil} if i18n.Translations[state.conj] then local rec = langSwitch(i18n.Translations[state.conj], lang) if type(rec)=='table' then case = rec.case end end date1 = oneDatePhrase(date1, state.adj1, state.era1, state.units1, lang, 1, case[1], state) date2 = oneDatePhrase(date2, state.adj2, state.era2, state.units2, lang, 2, case[2], state) dateStr = translatePhrase(date1, date2, state.conj, lang, state) if era ~= '' then dateStr = translatePhrase(dateStr, '', era, lang, state) end return dateStr end local function otherPhrases(date1, date2, operation, era, lang, state) -- translate specialized phrases local dateStr = '' if operation == 'islamic' then if date2=='' then date2 = mw.getCurrentFrame():callParserFunction('#time', 'xmY', date1) end date1 = getISODate(date1, operation, lang, 1, nil) date2 = getISODate(date2, operation, lang, 2, nil) if era == '' then era = 'ad' end dateStr = translatePhrase(date1, '', era, lang, state) .. ' (' .. translatePhrase(date2, '', 'ah', lang, state) .. ')' era = '' elseif operation == 'julian' then if not date2 and date1 then -- Convert from Julian to Gregorian calendar date if Calendar == nil then Calendar = require("Module:Calendar") end local JDN = Calendar._date2jdn(date1, 0) if JDN then date2 = date1 -- first date is assumed to be Julian date1 = Calendar._jdn2date(JDN, 1) end end date1 = getISODate(date1, operation, lang, 1, nil) date2 = getISODate(date2, operation, lang, 2, nil) dateStr = translatePhrase(date1, date2, operation, lang, state) dateStr = mw.ustring.gsub(mw.ustring.gsub(dateStr, '%( ', '('), ' %)', ')') -- in case date2 is empty elseif operation == 'turn of the year' or operation == 'turn of the decade' or operation == 'turn of the century' then if operation == 'turn of the decade' then dt=10 else dt=1 end if not date2 or date2=='' then date2=tostring(tonumber(date1)-dt) end if era~='bp' and era~='bc' then date1, date2 = date2, date1 end if operation == 'turn of the year' then date1 = ISOdate(date1, lang, '', '', 1) date2 = ISOdate(date2, lang, '', '', 1) else date1 = formatnum1(date1, lang) date2 = formatnum1(date2, lang) end dateStr = translatePhrase(date1, date2, operation, lang, state) elseif operation == 'year unknown' then dateStr = translatePhrase('', '', operation, lang, state) elseif operation == 'unknown' then dateStr = tostring(mw.message.new( "exif-unknowndate" ):inLanguage( lang )) end -- add era if era ~= '' then dateStr = translatePhrase(dateStr, '', era, lang, state) end return dateStr end local function checkAliases(str1, str2, sType) -- some inputs have many aliases - reconcile them and ensure string is playing a proper role local out = '' if str1 and str1~='' then a = i18n.Synonyms[str1] -- look up synonyms of "str1" if a then out = a[1] else p.Error = string.format('<span style="background-color:red;">Error in [[Module:Complex date]]: %s is not recognized.</span>', str1) end elseif str2 and str2~='' then -- if "str1" of type "sType" is empty than maybe ... a = i18n.Synonyms[str2] -- ..."str2" is of the same type and is not empty if a and a[2]==sType then out = a[1] str2 = '' end end return out, str2 end local function datePrecision(dateStr, units) -- "in this module "Units" is a string like millennium, century, or decade -- "precision" is wikibase compatible date precision number: 6=millennium, 7=century, 8=decade, 9=year, 10=month, 11=day -- based on string or numeric input calculate "Units" and "precision" local dateNum = tonumber(dateStr); if type(units)=='number' then precision = units if precision>11 then precision=11 end -- clip the range of precision values if precision==6 then units='millennium' elseif precision==7 then units='century' elseif precision==8 then units='decade' else units = '' end elseif type(units)=='string' then units = string.lower(units); if units=='millennium' then precision=6 elseif units=='century' then precision=7 elseif units=='decade' then precision=8 else precision=9 end end if units=='' or precision==9 then local sLen = mw.ustring.len(dateStr) if sLen<= 4 then precision=9 elseif sLen== 7 then precision=10 elseif sLen>=10 then precision=11 end units='' end if precision==6 and dateStr.match( dateStr, '%d000' )~=nil then dateStr = tostring(math.floor(tonumber(dateStr)/1000) +1) elseif precision==7 and mw.ustring.match( dateStr, '%d%d00' )~=nil then dateStr = tostring(math.floor(tonumber(dateStr)/100) +1) end return dateStr, units, precision end local function isodate2timestamp(dateStr, precision, era) -- convert date string to timestamps used by Quick Statements local tStamp = nil if era == 'ah' or precision<6 then return nil elseif era ~= '' then eraLUT = {ad='+', bc='-', bp='-' } era = eraLUT[era] else era='+' end -- convert isodate to timestamp used by quick statements if precision>=9 then if string.match(dateStr,"^%d%d%d%d$") then -- if YYYY format tStamp = era .. dateStr .. '-00-00T00:00:00Z/9' elseif string.match(dateStr,"^%d%d%d%d%-%d%d$") then -- if YYYY-MM format tStamp = era .. dateStr .. '-00T00:00:00Z/10' elseif string.match(dateStr,"^%d%d%d%d%-%d%d%-%d%d$") then -- if YYYY-MM-DD format tStamp = era .. dateStr .. 'T00:00:00Z/11' end elseif precision==8 then -- decade tStamp = era .. dateStr .. '-00-00T00:00:00Z/8' elseif precision==7 then -- century local d = tostring(tonumber(dateStr)-1) tStamp = era .. d .. '50-00-00T00:00:00Z/7' elseif precision==6 then local d = tostring(tonumber(dateStr)-1) tStamp = era .. d .. '500-00-00T00:00:00Z/6' end return tStamp end local function oneDateQScode(dateStr, adj, era, precision) -- create QuickStatements string for "one date" dates local outputStr = '' local d = isodate2timestamp(dateStr, precision, era) if not d then return '' end local rLUT = { early='Q40719727' , mid='Q40719748', late='Q40719766', ['1quarter']='Q40690303' , ['2quarter']='Q40719649' , ['3quarter']='Q40719662', ['4quarter']='Q40719674', spring='Q40720559' , summer='Q40720564' , autumn='Q40720568' , winter='Q40720553', firsthalf='Q40719687', secondhalf='Q40719707' } local qLUT = {['from']='P580', ['until']='P582', ['after']='P1319', ['before']='P1326'} local refine = rLUT[adj] local qualitier = qLUT[adj] if adj=='' then outputStr = d elseif adj=='circa' then outputStr = d..",P1480,Q5727902" elseif refine then outputStr = d..",P4241,"..refine elseif precision>7 and qualitier then local century = string.gsub(d, 'Z%/%d+', 'Z/7') outputStr = century ..",".. qualitier ..","..d end return outputStr end local function twoDateQScode(date1, date2, state) -- create QuickStatements string for "two date" dates if state.adj1~='' or state.adj2~='' or state.era1~=state.era2 then return '' end local outputStr = '' local d1 = isodate2timestamp(date1, state.precision1, state.era1) local d2 = isodate2timestamp(date2, state.precision2, state.era2) if (not d1) or (not d2) then return '' end -- find date with lower precision in common to both dates local cd local year1 = string.sub(d1,2,5) local k = 0 for i = 1,10,1 do if string.sub(d1,1,i)==string.sub(d2,1,i) then k = i -- find last matching letter end end if k>=9 then -- same month, since "+YYYY-MM-" is in common cd = isodate2timestamp(string.sub(d1,2,8), 10, state.era1) elseif k>=6 and k<9 then -- same year, since "+YYYY-" is in common cd = isodate2timestamp(year1, 9, state.era1) elseif k==4 then -- same decade(k=4, precision=8), since "+YYY" is in common cd = isodate2timestamp(year1, 8, state.era1) elseif k==3 then -- same century(k=3, precision=7) since "+YY" is in common local d = tostring(math.floor(tonumber(year1)/100) +1) -- convert 1999 -> 20 cd = isodate2timestamp( d, 7, state.era1) elseif k==2 then -- same millennium (k=2, precision=6), since "+Y" is in common local d = tostring(math.floor(tonumber(year1)/1000) +1) -- convert 1999 -> 2 cd = isodate2timestamp( d, 6, state.era1) else return '' end --if not cd then -- return ' <br/>error: ' .. d1.." / " .. d2.." / ".. (cd or '') .." / ".. string.sub(d1,2,5).." / " .. string.sub(d2,2,5).." / " .. tostring(k) --end -- if state.conj=='from-until' then outputStr = cd ..",P580,".. d1 ..",P582,".. d2 elseif state.conj=='between' then outputStr = cd ..",P1319,".. d1 ..",P1326,".. d2 elseif state.conj=='circa2' then outputStr = cd ..",P1319,".. d1 ..",P1326,".. d2 ..",P1480,Q5727902" end return outputStr end -- ================================================== -- === External functions =========================== -- ================================================== function p.Era(frame) -- process inputs local dateStr local args = frame.args if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then args.lang = frame:callParserFunction( "int", "lang" ) -- get user's chosen language end local lang = args['lang'] local dateStr = args['date'] or '' local eraType = string.lower(args['era'] or '') dateStr = ISOdate(dateStr, lang, '', '', 1) if eraType then eraType = checkAliases(eraType ,'','e') dateStr = translatePhrase(dateStr, '', eraType, lang, {}) end return dateStr end function p._complex_date(conj, adj1, date1, units1, era1, adj2, date2, units2, era2, lang, passNr) local Output='' -- process inputs and save date in state array local state = {} state.conj = string.lower(conj or '') state.adj1 = string.lower(adj1 or '') state.adj2 = string.lower(adj2 or '') state.era1 = string.lower(era1 or '') state.era2 = string.lower(era2 or '') state.units1 = string.lower(units1 or '') state.units2 = string.lower(units2 or '') -- if date 1 is missing but date 2 is provided than swap them if date1 == '' and date2 ~= '' then date1 = date2 date2 = '' state = {adj1 = state.adj2, era1 = state.era2, units1 = state.units2, adj2 = '', era2 = '', units2 = '', conj=state.conj, num=1} end if date2 ~= '' then state.nDates = 2 elseif date1 ~= '' then state.nDates = 1 else state.nDates = 0 end -- reconcile alternative names for text inputs local conj = checkAliases(state.conj ,'' ,'j') state.adj1 ,conj = checkAliases(state.adj1 ,conj,'a') state.units1,conj = checkAliases(state.units1,conj,'p') state.era1 ,conj = checkAliases(state.era1 ,conj,'e') state.special,conj = checkAliases('',conj,'c') state.adj2 = checkAliases(state.adj2 ,'','a') state.units2 = checkAliases(state.units2,'','p') state.era2 = checkAliases(state.era2 ,'','e') state.conj = conj state.lang = lang if p.Error~=nil then return nil end -- calculate date precision value date1, state.units1, state.precision1 = datePrecision(date1, state.units1) date2, state.units2, state.precision2 = datePrecision(date2, state.units2) -- Handle special cases -- Some complex phrases can be created out of simpler ones. Therefore on pass # 1 we try to create -- the phrase using complex phrase and if that is not found than on the second pass we try to build -- the phrase out of the simpler ones if passNr==1 then if state.adj1=='circa' and state.nDates == 2 then state.conj = 'circa2' state.adj1 = '' state.adj2 = '' end if state.nDates == 2 and state.adj1=='late' and state.adj2=='early' and state.conj=='and' and state.units1==state.units2 and state.era1==state.era2 then if state.units1=='century' then state.conj='turn of the century' elseif state.units1=='decade' then state.conj='turn of the decade' elseif state.units1=='' then state.conj='turn of the year' end state.adj1 = '' state.adj2 = '' state.units1 = '' state.units2 = '' end end local errorStr = string.format( '\n*conj=%s, adj1=%s, era1=%s, unit1=%s, prec1=%i, adj2=%s, era2=%s, unit2=%s, prec2=%i, special=%s', state.conj, state.adj1, state.era1, state.units1, state.precision1, state.adj2, state.era2, state.units2, state.precision2, state.special) state.adj, state.era, state.units, state.precision = state.adj1, state.era1, state.units1, state.precision1 -- call specialized functions local QScode = '' if state.special~='' then Output = otherPhrases(date1, date2, state.special, state.era1, lang, state) elseif state.conj~='' then QScode = twoDateQScode(date1, date2, state) Output = twoDatePhrase(date1, date2, state, lang) elseif state.adj1~='' or state.era1~='' or state.units1~='' then Output = oneDatePhrase(date1, state.adj1, state.era1, state.units1, lang, 1, nil, state) QScode = oneDateQScode(date1, state.adj1, state.era1, state.precision1) elseif date1~='' then Output = ISOdate(date1, lang, '', 'dtstart', '100-999') end if p.Error~=nil then return errorStr end -- if there is any wikicode in the string than execute it if mw.ustring.find(Output, '{') then Output = mw.getCurrentFrame():preprocess(Output) end if QScode and #QScode>0 then QScode = ' <div style="display: none;">date QS:P,' .. QScode .. '</div>' end return Output .. QScode end function p.complex_date(frame) -- process inputs local dateStr, Error local args = frame.args if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then args.lang = frame:callParserFunction( "int", "lang" ) -- get user's chosen language end local date1 = args['date1'] or args['2'] or args['date'] or '' local date2 = args['date2'] or args['3'] or '' local conj = args['conj'] or args['1'] or '' local adj1 = args['adj1'] or args['adj'] or '' local adj2 = args['adj2'] or '' local units1 = args['precision1'] or args['precision'] or '' local units2 = args['precision2'] or args['precision'] or '' local era1 = args['era1'] or args['era'] or '' local era2 = args['era2'] or args['era'] or '' local lang = args['lang'] dateStr = p._complex_date(conj, adj1, date1, units1, era1, adj2, date2, units2, era2, lang, 1) if p.Error~=nil then dateStr = p.Error .. '[[Category:Pages using Complex date template with incorrect parameter]]' end return dateStr end return p 9d1u6w6eaila3f7ct2gwiwxtjrg1eq3 803629 803628 2019-02-26T23:40:28Z en>RexxS 0 dt should be local {fix from Commons} 803629 Scribunto text/plain --[[ __ __ _ _ ____ _ _ _ | \/ | ___ __| |_ _| | ___ _ / ___|___ _ __ ___ _ __ | | _____ __ __| | __ _| |_ ___ | |\/| |/ _ \ / _` | | | | |/ _ (_) | / _ \| '_ ` _ \| '_ \| |/ _ \ \/ / / _` |/ _` | __/ _ \ | | | | (_) | (_| | |_| | | __/_| |__| (_) | | | | | | |_) | | __/> < | (_| | (_| | || __/ |_| |_|\___/ \__,_|\__,_|_|\___(_)\____\___/|_| |_| |_| .__/|_|\___/_/\_\ \__,_|\__,_|\__\___| |_| This module is intended for creation of complex date phrases in variety of languages. Once deployed, please do not modify this code without applying the changes first at Module:Complex date/sandbox and testing at Module:Complex date/sandbox/testcases. Authors and maintainers: * User:Sn1per - first draft of the original version * User:Jarekt - corrections and expansion of the original version ]] -- List of external modules and functions local p = {Error = nil} local i18n = require('Module:i18n/complex date') local ISOdate = require('Module:ISOdate')._ISOdate local Calendar -- loaded lazily -- ================================================== -- === Internal functions =========================== -- ================================================== local function langSwitch(list,lang) local langList = mw.language.getFallbacksFor(lang) table.insert(langList,1,lang) table.insert(langList,math.max(#langList,2),'default') for i,language in ipairs(langList) do if list[language] then return list[language] end end end local function formatnum1(numStr, lang) -- mostly require('Module:Formatnum').formatNum function used to translate a number to use different numeral characters, -- except that it it does not call that function unless the language is on the list "LList" local LList = {bn=1,bpy=1,kn=1,hi=1,mr=1,new=1,pa=1,gu=1,fa=1,glk=1,mzn=1,ur=1,ar=1,ckb=1,ks=1,lo=1,['or']=1,bo=1,['ml-old']=1,mn=1,te=1,th=1} if LList[lang] then -- call only when the language is on the list numStr = require('Module:Formatnum').formatNum(numStr, lang, 1) end return numStr end local function getISODate(datestr, datetype, lang, num, case) -- translate dates in the format YYYY, YYYY-MM, and YYYY-MM-DD if not case and i18n.Translations[datetype] then -- look up the grammatical case needed and call ISOdate module local rec = langSwitch(i18n.Translations[datetype], lang) if type(rec)=='table' then case = rec.case[num] end end return ISOdate(datestr, lang, case, '', 1) end local function translatePhrase(date1, date2, operation, lang, state) -- use tables in Module:i18n/complex date to translate a phrase if not i18n.Translations[operation] then p.Error = string.format('<span style="background-color:red;">Error in [[Module:Complex date]]: input parameter "%s" is not recognized.</span>', operation or 'nil') return '' end local dateStr = langSwitch(i18n.Translations[operation], lang) if type(dateStr)=='table' then dateStr = dateStr[1] end if type(dateStr)=='function' then local success local nDates = i18n.Translations[operation]['nDates'] if nDates==2 then -- 2 date phrase success, dateStr = pcall(dateStr, date1, date2, state) else -- 1 date phrase success, dateStr = pcall(dateStr, date1, state) end end if type(dateStr)=='string' then -- replace parts of the string '$date1' and '$date2' with date1 and date2 strings dateStr = mw.ustring.gsub(dateStr, '$date1', date1) dateStr = mw.ustring.gsub(dateStr, '$date2', date2) else -- Special case of more complex phrases that can be build out of simple phrases -- If complex case is not translated to "lang" than build it out of simpler ones local x = dateStr dateStr = p._complex_date(x.conj, x.adj1, date1, x.units1, x.era1, x.adj2, date2, x.units2, x.era2, lang, 2) end return dateStr end local function oneDatePhrase(dateStr, adj, era, units, lang, num, case, state) -- translate a single date phrase if num==2 then state.adj, state.era, state.units, state.precision = state.adj2, state.era2, state.units2, state.precision2 end -- dateStr can have many forms: ISO date, year or a number for -- decade, century or millennium if units == '' then -- unit is "year", "month", "day" dateStr = getISODate(dateStr, adj, lang, num, case) else -- units is "decade", "century", "millennium'' dateStr = translatePhrase(dateStr, '', units, lang, state) end -- add adjective ("early", "mid", etc.) or preposition ("before", "after", -- "circa", etc.) to the date if adj ~= '' then dateStr = translatePhrase(dateStr, '', adj, lang, state) else -- only era? dateStr = formatnum1(dateStr, lang) end -- add era if era ~= '' then dateStr = translatePhrase(dateStr, '', era, lang, state) end return dateStr end local function twoDatePhrase(date1, date2, state, lang) -- translate a double date phrase local dateStr, case local era='' if state.era1 == state.era2 then -- if both eras are the same than add it only once era = state.era1 state.era1 = '' state.era2 = '' end case = {nil, nil} if i18n.Translations[state.conj] then local rec = langSwitch(i18n.Translations[state.conj], lang) if type(rec)=='table' then case = rec.case end end date1 = oneDatePhrase(date1, state.adj1, state.era1, state.units1, lang, 1, case[1], state) date2 = oneDatePhrase(date2, state.adj2, state.era2, state.units2, lang, 2, case[2], state) dateStr = translatePhrase(date1, date2, state.conj, lang, state) if era ~= '' then dateStr = translatePhrase(dateStr, '', era, lang, state) end return dateStr end local function otherPhrases(date1, date2, operation, era, lang, state) -- translate specialized phrases local dateStr = '' if operation == 'islamic' then if date2=='' then date2 = mw.getCurrentFrame():callParserFunction('#time', 'xmY', date1) end date1 = getISODate(date1, operation, lang, 1, nil) date2 = getISODate(date2, operation, lang, 2, nil) if era == '' then era = 'ad' end dateStr = translatePhrase(date1, '', era, lang, state) .. ' (' .. translatePhrase(date2, '', 'ah', lang, state) .. ')' era = '' elseif operation == 'julian' then if not date2 and date1 then -- Convert from Julian to Gregorian calendar date if Calendar == nil then Calendar = require("Module:Calendar") end local JDN = Calendar._date2jdn(date1, 0) if JDN then date2 = date1 -- first date is assumed to be Julian date1 = Calendar._jdn2date(JDN, 1) end end date1 = getISODate(date1, operation, lang, 1, nil) date2 = getISODate(date2, operation, lang, 2, nil) dateStr = translatePhrase(date1, date2, operation, lang, state) dateStr = mw.ustring.gsub(mw.ustring.gsub(dateStr, '%( ', '('), ' %)', ')') -- in case date2 is empty elseif operation == 'turn of the year' or operation == 'turn of the decade' or operation == 'turn of the century' then local dt = 1 if operation == 'turn of the decade' then dt=10 end if not date2 or date2=='' then date2=tostring(tonumber(date1)-dt) end if era~='bp' and era~='bc' then date1, date2 = date2, date1 end if operation == 'turn of the year' then date1 = ISOdate(date1, lang, '', '', 1) date2 = ISOdate(date2, lang, '', '', 1) else date1 = formatnum1(date1, lang) date2 = formatnum1(date2, lang) end dateStr = translatePhrase(date1, date2, operation, lang, state) elseif operation == 'year unknown' then dateStr = translatePhrase('', '', operation, lang, state) elseif operation == 'unknown' then dateStr = tostring(mw.message.new( "exif-unknowndate" ):inLanguage( lang )) end -- add era if era ~= '' then dateStr = translatePhrase(dateStr, '', era, lang, state) end return dateStr end local function checkAliases(str1, str2, sType) -- some inputs have many aliases - reconcile them and ensure string is playing a proper role local out = '' if str1 and str1~='' then a = i18n.Synonyms[str1] -- look up synonyms of "str1" if a then out = a[1] else p.Error = string.format('<span style="background-color:red;">Error in [[Module:Complex date]]: %s is not recognized.</span>', str1) end elseif str2 and str2~='' then -- if "str1" of type "sType" is empty than maybe ... a = i18n.Synonyms[str2] -- ..."str2" is of the same type and is not empty if a and a[2]==sType then out = a[1] str2 = '' end end return out, str2 end local function datePrecision(dateStr, units) -- "in this module "Units" is a string like millennium, century, or decade -- "precision" is wikibase compatible date precision number: 6=millennium, 7=century, 8=decade, 9=year, 10=month, 11=day -- based on string or numeric input calculate "Units" and "precision" local dateNum = tonumber(dateStr); if type(units)=='number' then precision = units if precision>11 then precision=11 end -- clip the range of precision values if precision==6 then units='millennium' elseif precision==7 then units='century' elseif precision==8 then units='decade' else units = '' end elseif type(units)=='string' then units = string.lower(units); if units=='millennium' then precision=6 elseif units=='century' then precision=7 elseif units=='decade' then precision=8 else precision=9 end end if units=='' or precision==9 then local sLen = mw.ustring.len(dateStr) if sLen<= 4 then precision=9 elseif sLen== 7 then precision=10 elseif sLen>=10 then precision=11 end units='' end if precision==6 and dateStr.match( dateStr, '%d000' )~=nil then dateStr = tostring(math.floor(tonumber(dateStr)/1000) +1) elseif precision==7 and mw.ustring.match( dateStr, '%d%d00' )~=nil then dateStr = tostring(math.floor(tonumber(dateStr)/100) +1) end return dateStr, units, precision end local function isodate2timestamp(dateStr, precision, era) -- convert date string to timestamps used by Quick Statements local tStamp = nil if era == 'ah' or precision<6 then return nil elseif era ~= '' then eraLUT = {ad='+', bc='-', bp='-' } era = eraLUT[era] else era='+' end -- convert isodate to timestamp used by quick statements if precision>=9 then if string.match(dateStr,"^%d%d%d%d$") then -- if YYYY format tStamp = era .. dateStr .. '-00-00T00:00:00Z/9' elseif string.match(dateStr,"^%d%d%d%d%-%d%d$") then -- if YYYY-MM format tStamp = era .. dateStr .. '-00T00:00:00Z/10' elseif string.match(dateStr,"^%d%d%d%d%-%d%d%-%d%d$") then -- if YYYY-MM-DD format tStamp = era .. dateStr .. 'T00:00:00Z/11' end elseif precision==8 then -- decade tStamp = era .. dateStr .. '-00-00T00:00:00Z/8' elseif precision==7 then -- century local d = tostring(tonumber(dateStr)-1) tStamp = era .. d .. '50-00-00T00:00:00Z/7' elseif precision==6 then local d = tostring(tonumber(dateStr)-1) tStamp = era .. d .. '500-00-00T00:00:00Z/6' end return tStamp end local function oneDateQScode(dateStr, adj, era, precision) -- create QuickStatements string for "one date" dates local outputStr = '' local d = isodate2timestamp(dateStr, precision, era) if not d then return '' end local rLUT = { early='Q40719727' , mid='Q40719748', late='Q40719766', ['1quarter']='Q40690303' , ['2quarter']='Q40719649' , ['3quarter']='Q40719662', ['4quarter']='Q40719674', spring='Q40720559' , summer='Q40720564' , autumn='Q40720568' , winter='Q40720553', firsthalf='Q40719687', secondhalf='Q40719707' } local qLUT = {['from']='P580', ['until']='P582', ['after']='P1319', ['before']='P1326'} local refine = rLUT[adj] local qualitier = qLUT[adj] if adj=='' then outputStr = d elseif adj=='circa' then outputStr = d..",P1480,Q5727902" elseif refine then outputStr = d..",P4241,"..refine elseif precision>7 and qualitier then local century = string.gsub(d, 'Z%/%d+', 'Z/7') outputStr = century ..",".. qualitier ..","..d end return outputStr end local function twoDateQScode(date1, date2, state) -- create QuickStatements string for "two date" dates if state.adj1~='' or state.adj2~='' or state.era1~=state.era2 then return '' end local outputStr = '' local d1 = isodate2timestamp(date1, state.precision1, state.era1) local d2 = isodate2timestamp(date2, state.precision2, state.era2) if (not d1) or (not d2) then return '' end -- find date with lower precision in common to both dates local cd local year1 = string.sub(d1,2,5) local k = 0 for i = 1,10,1 do if string.sub(d1,1,i)==string.sub(d2,1,i) then k = i -- find last matching letter end end if k>=9 then -- same month, since "+YYYY-MM-" is in common cd = isodate2timestamp(string.sub(d1,2,8), 10, state.era1) elseif k>=6 and k<9 then -- same year, since "+YYYY-" is in common cd = isodate2timestamp(year1, 9, state.era1) elseif k==4 then -- same decade(k=4, precision=8), since "+YYY" is in common cd = isodate2timestamp(year1, 8, state.era1) elseif k==3 then -- same century(k=3, precision=7) since "+YY" is in common local d = tostring(math.floor(tonumber(year1)/100) +1) -- convert 1999 -> 20 cd = isodate2timestamp( d, 7, state.era1) elseif k==2 then -- same millennium (k=2, precision=6), since "+Y" is in common local d = tostring(math.floor(tonumber(year1)/1000) +1) -- convert 1999 -> 2 cd = isodate2timestamp( d, 6, state.era1) else return '' end --if not cd then -- return ' <br/>error: ' .. d1.." / " .. d2.." / ".. (cd or '') .." / ".. string.sub(d1,2,5).." / " .. string.sub(d2,2,5).." / " .. tostring(k) --end -- if state.conj=='from-until' then outputStr = cd ..",P580,".. d1 ..",P582,".. d2 elseif state.conj=='between' then outputStr = cd ..",P1319,".. d1 ..",P1326,".. d2 elseif state.conj=='circa2' then outputStr = cd ..",P1319,".. d1 ..",P1326,".. d2 ..",P1480,Q5727902" end return outputStr end -- ================================================== -- === External functions =========================== -- ================================================== function p.Era(frame) -- process inputs local dateStr local args = frame.args if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then args.lang = frame:callParserFunction( "int", "lang" ) -- get user's chosen language end local lang = args['lang'] local dateStr = args['date'] or '' local eraType = string.lower(args['era'] or '') dateStr = ISOdate(dateStr, lang, '', '', 1) if eraType then eraType = checkAliases(eraType ,'','e') dateStr = translatePhrase(dateStr, '', eraType, lang, {}) end return dateStr end function p._complex_date(conj, adj1, date1, units1, era1, adj2, date2, units2, era2, lang, passNr) local Output='' -- process inputs and save date in state array local state = {} state.conj = string.lower(conj or '') state.adj1 = string.lower(adj1 or '') state.adj2 = string.lower(adj2 or '') state.era1 = string.lower(era1 or '') state.era2 = string.lower(era2 or '') state.units1 = string.lower(units1 or '') state.units2 = string.lower(units2 or '') -- if date 1 is missing but date 2 is provided than swap them if date1 == '' and date2 ~= '' then date1 = date2 date2 = '' state = {adj1 = state.adj2, era1 = state.era2, units1 = state.units2, adj2 = '', era2 = '', units2 = '', conj=state.conj, num=1} end if date2 ~= '' then state.nDates = 2 elseif date1 ~= '' then state.nDates = 1 else state.nDates = 0 end -- reconcile alternative names for text inputs local conj = checkAliases(state.conj ,'' ,'j') state.adj1 ,conj = checkAliases(state.adj1 ,conj,'a') state.units1,conj = checkAliases(state.units1,conj,'p') state.era1 ,conj = checkAliases(state.era1 ,conj,'e') state.special,conj = checkAliases('',conj,'c') state.adj2 = checkAliases(state.adj2 ,'','a') state.units2 = checkAliases(state.units2,'','p') state.era2 = checkAliases(state.era2 ,'','e') state.conj = conj state.lang = lang if p.Error~=nil then return nil end -- calculate date precision value date1, state.units1, state.precision1 = datePrecision(date1, state.units1) date2, state.units2, state.precision2 = datePrecision(date2, state.units2) -- Handle special cases -- Some complex phrases can be created out of simpler ones. Therefore on pass # 1 we try to create -- the phrase using complex phrase and if that is not found than on the second pass we try to build -- the phrase out of the simpler ones if passNr==1 then if state.adj1=='circa' and state.nDates == 2 then state.conj = 'circa2' state.adj1 = '' state.adj2 = '' end if state.nDates == 2 and state.adj1=='late' and state.adj2=='early' and state.conj=='and' and state.units1==state.units2 and state.era1==state.era2 then if state.units1=='century' then state.conj='turn of the century' elseif state.units1=='decade' then state.conj='turn of the decade' elseif state.units1=='' then state.conj='turn of the year' end state.adj1 = '' state.adj2 = '' state.units1 = '' state.units2 = '' end end local errorStr = string.format( '\n*conj=%s, adj1=%s, era1=%s, unit1=%s, prec1=%i, adj2=%s, era2=%s, unit2=%s, prec2=%i, special=%s', state.conj, state.adj1, state.era1, state.units1, state.precision1, state.adj2, state.era2, state.units2, state.precision2, state.special) state.adj, state.era, state.units, state.precision = state.adj1, state.era1, state.units1, state.precision1 -- call specialized functions local QScode = '' if state.special~='' then Output = otherPhrases(date1, date2, state.special, state.era1, lang, state) elseif state.conj~='' then QScode = twoDateQScode(date1, date2, state) Output = twoDatePhrase(date1, date2, state, lang) elseif state.adj1~='' or state.era1~='' or state.units1~='' then Output = oneDatePhrase(date1, state.adj1, state.era1, state.units1, lang, 1, nil, state) QScode = oneDateQScode(date1, state.adj1, state.era1, state.precision1) elseif date1~='' then Output = ISOdate(date1, lang, '', 'dtstart', '100-999') end if p.Error~=nil then return errorStr end -- if there is any wikicode in the string than execute it if mw.ustring.find(Output, '{') then Output = mw.getCurrentFrame():preprocess(Output) end if QScode and #QScode>0 then QScode = ' <div style="display: none;">date QS:P,' .. QScode .. '</div>' end return Output .. QScode end function p.complex_date(frame) -- process inputs local dateStr, Error local args = frame.args if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then args.lang = frame:callParserFunction( "int", "lang" ) -- get user's chosen language end local date1 = args['date1'] or args['2'] or args['date'] or '' local date2 = args['date2'] or args['3'] or '' local conj = args['conj'] or args['1'] or '' local adj1 = args['adj1'] or args['adj'] or '' local adj2 = args['adj2'] or '' local units1 = args['precision1'] or args['precision'] or '' local units2 = args['precision2'] or args['precision'] or '' local era1 = args['era1'] or args['era'] or '' local era2 = args['era2'] or args['era'] or '' local lang = args['lang'] dateStr = p._complex_date(conj, adj1, date1, units1, era1, adj2, date2, units2, era2, lang, 1) if p.Error~=nil then dateStr = p.Error .. '[[Category:Pages using Complex date template with incorrect parameter]]' end return dateStr end return p 8iu59wdkuz6zpa1pc8c4qaykfl5puov 803630 803629 2020-05-14T21:02:23Z en>RexxS 0 no globals - change to local 803630 Scribunto text/plain --[[ __ __ _ _ ____ _ _ _ | \/ | ___ __| |_ _| | ___ _ / ___|___ _ __ ___ _ __ | | _____ __ __| | __ _| |_ ___ | |\/| |/ _ \ / _` | | | | |/ _ (_) | / _ \| '_ ` _ \| '_ \| |/ _ \ \/ / / _` |/ _` | __/ _ \ | | | | (_) | (_| | |_| | | __/_| |__| (_) | | | | | | |_) | | __/> < | (_| | (_| | || __/ |_| |_|\___/ \__,_|\__,_|_|\___(_)\____\___/|_| |_| |_| .__/|_|\___/_/\_\ \__,_|\__,_|\__\___| |_| This module is intended for creation of complex date phrases in variety of languages. Once deployed, please do not modify this code without applying the changes first at Module:Complex date/sandbox and testing at Module:Complex date/sandbox/testcases. Authors and maintainers: * User:Sn1per - first draft of the original version * User:Jarekt - corrections and expansion of the original version ]] -- List of external modules and functions local p = {Error = nil} local i18n = require('Module:i18n/complex date') local ISOdate = require('Module:ISOdate')._ISOdate local Calendar -- loaded lazily -- ================================================== -- === Internal functions =========================== -- ================================================== local function langSwitch(list,lang) local langList = mw.language.getFallbacksFor(lang) table.insert(langList,1,lang) table.insert(langList,math.max(#langList,2),'default') for i,language in ipairs(langList) do if list[language] then return list[language] end end end local function formatnum1(numStr, lang) -- mostly require('Module:Formatnum').formatNum function used to translate a number to use different numeral characters, -- except that it it does not call that function unless the language is on the list "LList" local LList = {bn=1,bpy=1,kn=1,hi=1,mr=1,new=1,pa=1,gu=1,fa=1,glk=1,mzn=1,ur=1,ar=1,ckb=1,ks=1,lo=1,['or']=1,bo=1,['ml-old']=1,mn=1,te=1,th=1} if LList[lang] then -- call only when the language is on the list numStr = require('Module:Formatnum').formatNum(numStr, lang, 1) end return numStr end local function getISODate(datestr, datetype, lang, num, case) -- translate dates in the format YYYY, YYYY-MM, and YYYY-MM-DD if not case and i18n.Translations[datetype] then -- look up the grammatical case needed and call ISOdate module local rec = langSwitch(i18n.Translations[datetype], lang) if type(rec)=='table' then case = rec.case[num] end end return ISOdate(datestr, lang, case, '', 1) end local function translatePhrase(date1, date2, operation, lang, state) -- use tables in Module:i18n/complex date to translate a phrase if not i18n.Translations[operation] then p.Error = string.format('<span style="background-color:red;">Error in [[Module:Complex date]]: input parameter "%s" is not recognized.</span>', operation or 'nil') return '' end local dateStr = langSwitch(i18n.Translations[operation], lang) if type(dateStr)=='table' then dateStr = dateStr[1] end if type(dateStr)=='function' then local success local nDates = i18n.Translations[operation]['nDates'] if nDates==2 then -- 2 date phrase success, dateStr = pcall(dateStr, date1, date2, state) else -- 1 date phrase success, dateStr = pcall(dateStr, date1, state) end end if type(dateStr)=='string' then -- replace parts of the string '$date1' and '$date2' with date1 and date2 strings dateStr = mw.ustring.gsub(dateStr, '$date1', date1) dateStr = mw.ustring.gsub(dateStr, '$date2', date2) else -- Special case of more complex phrases that can be build out of simple phrases -- If complex case is not translated to "lang" than build it out of simpler ones local x = dateStr dateStr = p._complex_date(x.conj, x.adj1, date1, x.units1, x.era1, x.adj2, date2, x.units2, x.era2, lang, 2) end return dateStr end local function oneDatePhrase(dateStr, adj, era, units, lang, num, case, state) -- translate a single date phrase if num==2 then state.adj, state.era, state.units, state.precision = state.adj2, state.era2, state.units2, state.precision2 end -- dateStr can have many forms: ISO date, year or a number for -- decade, century or millennium if units == '' then -- unit is "year", "month", "day" dateStr = getISODate(dateStr, adj, lang, num, case) else -- units is "decade", "century", "millennium'' dateStr = translatePhrase(dateStr, '', units, lang, state) end -- add adjective ("early", "mid", etc.) or preposition ("before", "after", -- "circa", etc.) to the date if adj ~= '' then dateStr = translatePhrase(dateStr, '', adj, lang, state) else -- only era? dateStr = formatnum1(dateStr, lang) end -- add era if era ~= '' then dateStr = translatePhrase(dateStr, '', era, lang, state) end return dateStr end local function twoDatePhrase(date1, date2, state, lang) -- translate a double date phrase local dateStr, case local era='' if state.era1 == state.era2 then -- if both eras are the same than add it only once era = state.era1 state.era1 = '' state.era2 = '' end case = {nil, nil} if i18n.Translations[state.conj] then local rec = langSwitch(i18n.Translations[state.conj], lang) if type(rec)=='table' then case = rec.case end end date1 = oneDatePhrase(date1, state.adj1, state.era1, state.units1, lang, 1, case[1], state) date2 = oneDatePhrase(date2, state.adj2, state.era2, state.units2, lang, 2, case[2], state) dateStr = translatePhrase(date1, date2, state.conj, lang, state) if era ~= '' then dateStr = translatePhrase(dateStr, '', era, lang, state) end return dateStr end local function otherPhrases(date1, date2, operation, era, lang, state) -- translate specialized phrases local dateStr = '' if operation == 'islamic' then if date2=='' then date2 = mw.getCurrentFrame():callParserFunction('#time', 'xmY', date1) end date1 = getISODate(date1, operation, lang, 1, nil) date2 = getISODate(date2, operation, lang, 2, nil) if era == '' then era = 'ad' end dateStr = translatePhrase(date1, '', era, lang, state) .. ' (' .. translatePhrase(date2, '', 'ah', lang, state) .. ')' era = '' elseif operation == 'julian' then if not date2 and date1 then -- Convert from Julian to Gregorian calendar date if Calendar == nil then Calendar = require("Module:Calendar") end local JDN = Calendar._date2jdn(date1, 0) if JDN then date2 = date1 -- first date is assumed to be Julian date1 = Calendar._jdn2date(JDN, 1) end end date1 = getISODate(date1, operation, lang, 1, nil) date2 = getISODate(date2, operation, lang, 2, nil) dateStr = translatePhrase(date1, date2, operation, lang, state) dateStr = mw.ustring.gsub(mw.ustring.gsub(dateStr, '%( ', '('), ' %)', ')') -- in case date2 is empty elseif operation == 'turn of the year' or operation == 'turn of the decade' or operation == 'turn of the century' then local dt = 1 if operation == 'turn of the decade' then dt=10 end if not date2 or date2=='' then date2=tostring(tonumber(date1)-dt) end if era~='bp' and era~='bc' then date1, date2 = date2, date1 end if operation == 'turn of the year' then date1 = ISOdate(date1, lang, '', '', 1) date2 = ISOdate(date2, lang, '', '', 1) else date1 = formatnum1(date1, lang) date2 = formatnum1(date2, lang) end dateStr = translatePhrase(date1, date2, operation, lang, state) elseif operation == 'year unknown' then dateStr = translatePhrase('', '', operation, lang, state) elseif operation == 'unknown' then dateStr = tostring(mw.message.new( "exif-unknowndate" ):inLanguage( lang )) end -- add era if era ~= '' then dateStr = translatePhrase(dateStr, '', era, lang, state) end return dateStr end local function checkAliases(str1, str2, sType) -- some inputs have many aliases - reconcile them and ensure string is playing a proper role local out = '' if str1 and str1~='' then local a = i18n.Synonyms[str1] -- look up synonyms of "str1" if a then out = a[1] else p.Error = string.format('<span style="background-color:red;">Error in [[Module:Complex date]]: %s is not recognized.</span>', str1) end elseif str2 and str2~='' then -- if "str1" of type "sType" is empty than maybe ... a = i18n.Synonyms[str2] -- ..."str2" is of the same type and is not empty if a and a[2]==sType then out = a[1] str2 = '' end end return out, str2 end local function datePrecision(dateStr, units) -- "in this module "Units" is a string like millennium, century, or decade -- "precision" is wikibase compatible date precision number: 6=millennium, 7=century, 8=decade, 9=year, 10=month, 11=day -- based on string or numeric input calculate "Units" and "precision" local dateNum = tonumber(dateStr) local precision if type(units)=='number' then precision = units if precision>11 then precision=11 end -- clip the range of precision values if precision==6 then units='millennium' elseif precision==7 then units='century' elseif precision==8 then units='decade' else units = '' end elseif type(units)=='string' then units = string.lower(units); if units=='millennium' then precision=6 elseif units=='century' then precision=7 elseif units=='decade' then precision=8 else precision=9 end end if units=='' or precision==9 then local sLen = mw.ustring.len(dateStr) if sLen<= 4 then precision=9 elseif sLen== 7 then precision=10 elseif sLen>=10 then precision=11 end units='' end if precision==6 and dateStr.match( dateStr, '%d000' )~=nil then dateStr = tostring(math.floor(tonumber(dateStr)/1000) +1) elseif precision==7 and mw.ustring.match( dateStr, '%d%d00' )~=nil then dateStr = tostring(math.floor(tonumber(dateStr)/100) +1) end return dateStr, units, precision end local function isodate2timestamp(dateStr, precision, era) -- convert date string to timestamps used by Quick Statements local tStamp = nil if era == 'ah' or precision<6 then return nil elseif era ~= '' then local eraLUT = {ad='+', bc='-', bp='-' } era = eraLUT[era] else era='+' end -- convert isodate to timestamp used by quick statements if precision>=9 then if string.match(dateStr,"^%d%d%d%d$") then -- if YYYY format tStamp = era .. dateStr .. '-00-00T00:00:00Z/9' elseif string.match(dateStr,"^%d%d%d%d%-%d%d$") then -- if YYYY-MM format tStamp = era .. dateStr .. '-00T00:00:00Z/10' elseif string.match(dateStr,"^%d%d%d%d%-%d%d%-%d%d$") then -- if YYYY-MM-DD format tStamp = era .. dateStr .. 'T00:00:00Z/11' end elseif precision==8 then -- decade tStamp = era .. dateStr .. '-00-00T00:00:00Z/8' elseif precision==7 then -- century local d = tostring(tonumber(dateStr)-1) tStamp = era .. d .. '50-00-00T00:00:00Z/7' elseif precision==6 then local d = tostring(tonumber(dateStr)-1) tStamp = era .. d .. '500-00-00T00:00:00Z/6' end return tStamp end local function oneDateQScode(dateStr, adj, era, precision) -- create QuickStatements string for "one date" dates local outputStr = '' local d = isodate2timestamp(dateStr, precision, era) if not d then return '' end local rLUT = { early='Q40719727' , mid='Q40719748', late='Q40719766', ['1quarter']='Q40690303' , ['2quarter']='Q40719649' , ['3quarter']='Q40719662', ['4quarter']='Q40719674', spring='Q40720559' , summer='Q40720564' , autumn='Q40720568' , winter='Q40720553', firsthalf='Q40719687', secondhalf='Q40719707' } local qLUT = {['from']='P580', ['until']='P582', ['after']='P1319', ['before']='P1326'} local refine = rLUT[adj] local qualitier = qLUT[adj] if adj=='' then outputStr = d elseif adj=='circa' then outputStr = d..",P1480,Q5727902" elseif refine then outputStr = d..",P4241,"..refine elseif precision>7 and qualitier then local century = string.gsub(d, 'Z%/%d+', 'Z/7') outputStr = century ..",".. qualitier ..","..d end return outputStr end local function twoDateQScode(date1, date2, state) -- create QuickStatements string for "two date" dates if state.adj1~='' or state.adj2~='' or state.era1~=state.era2 then return '' end local outputStr = '' local d1 = isodate2timestamp(date1, state.precision1, state.era1) local d2 = isodate2timestamp(date2, state.precision2, state.era2) if (not d1) or (not d2) then return '' end -- find date with lower precision in common to both dates local cd local year1 = string.sub(d1,2,5) local k = 0 for i = 1,10,1 do if string.sub(d1,1,i)==string.sub(d2,1,i) then k = i -- find last matching letter end end if k>=9 then -- same month, since "+YYYY-MM-" is in common cd = isodate2timestamp(string.sub(d1,2,8), 10, state.era1) elseif k>=6 and k<9 then -- same year, since "+YYYY-" is in common cd = isodate2timestamp(year1, 9, state.era1) elseif k==4 then -- same decade(k=4, precision=8), since "+YYY" is in common cd = isodate2timestamp(year1, 8, state.era1) elseif k==3 then -- same century(k=3, precision=7) since "+YY" is in common local d = tostring(math.floor(tonumber(year1)/100) +1) -- convert 1999 -> 20 cd = isodate2timestamp( d, 7, state.era1) elseif k==2 then -- same millennium (k=2, precision=6), since "+Y" is in common local d = tostring(math.floor(tonumber(year1)/1000) +1) -- convert 1999 -> 2 cd = isodate2timestamp( d, 6, state.era1) else return '' end --if not cd then -- return ' <br/>error: ' .. d1.." / " .. d2.." / ".. (cd or '') .." / ".. string.sub(d1,2,5).." / " .. string.sub(d2,2,5).." / " .. tostring(k) --end -- if state.conj=='from-until' then outputStr = cd ..",P580,".. d1 ..",P582,".. d2 elseif state.conj=='between' then outputStr = cd ..",P1319,".. d1 ..",P1326,".. d2 elseif state.conj=='circa2' then outputStr = cd ..",P1319,".. d1 ..",P1326,".. d2 ..",P1480,Q5727902" end return outputStr end -- ================================================== -- === External functions =========================== -- ================================================== function p.Era(frame) -- process inputs local dateStr local args = frame.args if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then args.lang = frame:callParserFunction( "int", "lang" ) -- get user's chosen language end local lang = args['lang'] local dateStr = args['date'] or '' local eraType = string.lower(args['era'] or '') dateStr = ISOdate(dateStr, lang, '', '', 1) if eraType then eraType = checkAliases(eraType ,'','e') dateStr = translatePhrase(dateStr, '', eraType, lang, {}) end return dateStr end function p._complex_date(conj, adj1, date1, units1, era1, adj2, date2, units2, era2, lang, passNr) local Output='' -- process inputs and save date in state array local state = {} state.conj = string.lower(conj or '') state.adj1 = string.lower(adj1 or '') state.adj2 = string.lower(adj2 or '') state.era1 = string.lower(era1 or '') state.era2 = string.lower(era2 or '') state.units1 = string.lower(units1 or '') state.units2 = string.lower(units2 or '') -- if date 1 is missing but date 2 is provided than swap them if date1 == '' and date2 ~= '' then date1 = date2 date2 = '' state = {adj1 = state.adj2, era1 = state.era2, units1 = state.units2, adj2 = '', era2 = '', units2 = '', conj=state.conj, num=1} end if date2 ~= '' then state.nDates = 2 elseif date1 ~= '' then state.nDates = 1 else state.nDates = 0 end -- reconcile alternative names for text inputs local conj = checkAliases(state.conj ,'' ,'j') state.adj1 ,conj = checkAliases(state.adj1 ,conj,'a') state.units1,conj = checkAliases(state.units1,conj,'p') state.era1 ,conj = checkAliases(state.era1 ,conj,'e') state.special,conj = checkAliases('',conj,'c') state.adj2 = checkAliases(state.adj2 ,'','a') state.units2 = checkAliases(state.units2,'','p') state.era2 = checkAliases(state.era2 ,'','e') state.conj = conj state.lang = lang if p.Error~=nil then return nil end -- calculate date precision value date1, state.units1, state.precision1 = datePrecision(date1, state.units1) date2, state.units2, state.precision2 = datePrecision(date2, state.units2) -- Handle special cases -- Some complex phrases can be created out of simpler ones. Therefore on pass # 1 we try to create -- the phrase using complex phrase and if that is not found than on the second pass we try to build -- the phrase out of the simpler ones if passNr==1 then if state.adj1=='circa' and state.nDates == 2 then state.conj = 'circa2' state.adj1 = '' state.adj2 = '' end if state.nDates == 2 and state.adj1=='late' and state.adj2=='early' and state.conj=='and' and state.units1==state.units2 and state.era1==state.era2 then if state.units1=='century' then state.conj='turn of the century' elseif state.units1=='decade' then state.conj='turn of the decade' elseif state.units1=='' then state.conj='turn of the year' end state.adj1 = '' state.adj2 = '' state.units1 = '' state.units2 = '' end end local errorStr = string.format( '\n*conj=%s, adj1=%s, era1=%s, unit1=%s, prec1=%i, adj2=%s, era2=%s, unit2=%s, prec2=%i, special=%s', state.conj, state.adj1, state.era1, state.units1, state.precision1, state.adj2, state.era2, state.units2, state.precision2, state.special) state.adj, state.era, state.units, state.precision = state.adj1, state.era1, state.units1, state.precision1 -- call specialized functions local QScode = '' if state.special~='' then Output = otherPhrases(date1, date2, state.special, state.era1, lang, state) elseif state.conj~='' then QScode = twoDateQScode(date1, date2, state) Output = twoDatePhrase(date1, date2, state, lang) elseif state.adj1~='' or state.era1~='' or state.units1~='' then Output = oneDatePhrase(date1, state.adj1, state.era1, state.units1, lang, 1, nil, state) QScode = oneDateQScode(date1, state.adj1, state.era1, state.precision1) elseif date1~='' then Output = ISOdate(date1, lang, '', 'dtstart', '100-999') end if p.Error~=nil then return errorStr end -- if there is any wikicode in the string than execute it if mw.ustring.find(Output, '{') then Output = mw.getCurrentFrame():preprocess(Output) end if QScode and #QScode>0 then QScode = ' <div style="display: none;">date QS:P,' .. QScode .. '</div>' end return Output .. QScode end function p.complex_date(frame) -- process inputs local dateStr, Error local args = frame.args if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then args.lang = frame:callParserFunction( "int", "lang" ) -- get user's chosen language end local date1 = args['date1'] or args['2'] or args['date'] or '' local date2 = args['date2'] or args['3'] or '' local conj = args['conj'] or args['1'] or '' local adj1 = args['adj1'] or args['adj'] or '' local adj2 = args['adj2'] or '' local units1 = args['precision1'] or args['precision'] or '' local units2 = args['precision2'] or args['precision'] or '' local era1 = args['era1'] or args['era'] or '' local era2 = args['era2'] or args['era'] or '' local lang = args['lang'] dateStr = p._complex_date(conj, adj1, date1, units1, era1, adj2, date2, units2, era2, lang, 1) if p.Error~=nil then dateStr = p.Error .. '[[Category:Pages using Complex date template with incorrect parameter]]' end return dateStr end return p 1kjebocnp7tu6ux43orofpvdbuj54mn 803631 803630 2020-05-15T03:54:48Z en>Johnuniq 0 clean whitespace 803631 Scribunto text/plain --[[ __ __ _ _ ____ _ _ _ | \/ | ___ __| |_ _| | ___ _ / ___|___ _ __ ___ _ __ | | _____ __ __| | __ _| |_ ___ | |\/| |/ _ \ / _` | | | | |/ _ (_) | / _ \| '_ ` _ \| '_ \| |/ _ \ \/ / / _` |/ _` | __/ _ \ | | | | (_) | (_| | |_| | | __/_| |__| (_) | | | | | | |_) | | __/> < | (_| | (_| | || __/ |_| |_|\___/ \__,_|\__,_|_|\___(_)\____\___/|_| |_| |_| .__/|_|\___/_/\_\ \__,_|\__,_|\__\___| |_| This module is intended for creation of complex date phrases in variety of languages. Once deployed, please do not modify this code without applying the changes first at Module:Complex date/sandbox and testing at Module:Complex date/sandbox/testcases. Authors and maintainers: * User:Sn1per - first draft of the original version * User:Jarekt - corrections and expansion of the original version ]] -- List of external modules and functions local p = {Error = nil} local i18n = require('Module:i18n/complex date') local ISOdate = require('Module:ISOdate')._ISOdate local Calendar -- loaded lazily -- ================================================== -- === Internal functions =========================== -- ================================================== local function langSwitch(list,lang) local langList = mw.language.getFallbacksFor(lang) table.insert(langList,1,lang) table.insert(langList,math.max(#langList,2),'default') for i,language in ipairs(langList) do if list[language] then return list[language] end end end local function formatnum1(numStr, lang) -- mostly require('Module:Formatnum').formatNum function used to translate a number to use different numeral characters, -- except that it it does not call that function unless the language is on the list "LList" local LList = {bn=1,bpy=1,kn=1,hi=1,mr=1,new=1,pa=1,gu=1,fa=1,glk=1,mzn=1,ur=1,ar=1,ckb=1,ks=1,lo=1,['or']=1,bo=1,['ml-old']=1,mn=1,te=1,th=1} if LList[lang] then -- call only when the language is on the list numStr = require('Module:Formatnum').formatNum(numStr, lang, 1) end return numStr end local function getISODate(datestr, datetype, lang, num, case) -- translate dates in the format YYYY, YYYY-MM, and YYYY-MM-DD if not case and i18n.Translations[datetype] then -- look up the grammatical case needed and call ISOdate module local rec = langSwitch(i18n.Translations[datetype], lang) if type(rec)=='table' then case = rec.case[num] end end return ISOdate(datestr, lang, case, '', 1) end local function translatePhrase(date1, date2, operation, lang, state) -- use tables in Module:i18n/complex date to translate a phrase if not i18n.Translations[operation] then p.Error = string.format('<span style="background-color:red;">Error in [[Module:Complex date]]: input parameter "%s" is not recognized.</span>', operation or 'nil') return '' end local dateStr = langSwitch(i18n.Translations[operation], lang) if type(dateStr)=='table' then dateStr = dateStr[1] end if type(dateStr)=='function' then local success local nDates = i18n.Translations[operation]['nDates'] if nDates==2 then -- 2 date phrase success, dateStr = pcall(dateStr, date1, date2, state) else -- 1 date phrase success, dateStr = pcall(dateStr, date1, state) end end if type(dateStr)=='string' then -- replace parts of the string '$date1' and '$date2' with date1 and date2 strings dateStr = mw.ustring.gsub(dateStr, '$date1', date1) dateStr = mw.ustring.gsub(dateStr, '$date2', date2) else -- Special case of more complex phrases that can be build out of simple phrases -- If complex case is not translated to "lang" than build it out of simpler ones local x = dateStr dateStr = p._complex_date(x.conj, x.adj1, date1, x.units1, x.era1, x.adj2, date2, x.units2, x.era2, lang, 2) end return dateStr end local function oneDatePhrase(dateStr, adj, era, units, lang, num, case, state) -- translate a single date phrase if num==2 then state.adj, state.era, state.units, state.precision = state.adj2, state.era2, state.units2, state.precision2 end -- dateStr can have many forms: ISO date, year or a number for -- decade, century or millennium if units == '' then -- unit is "year", "month", "day" dateStr = getISODate(dateStr, adj, lang, num, case) else -- units is "decade", "century", "millennium'' dateStr = translatePhrase(dateStr, '', units, lang, state) end -- add adjective ("early", "mid", etc.) or preposition ("before", "after", -- "circa", etc.) to the date if adj ~= '' then dateStr = translatePhrase(dateStr, '', adj, lang, state) else -- only era? dateStr = formatnum1(dateStr, lang) end -- add era if era ~= '' then dateStr = translatePhrase(dateStr, '', era, lang, state) end return dateStr end local function twoDatePhrase(date1, date2, state, lang) -- translate a double date phrase local dateStr, case local era='' if state.era1 == state.era2 then -- if both eras are the same than add it only once era = state.era1 state.era1 = '' state.era2 = '' end case = {nil, nil} if i18n.Translations[state.conj] then local rec = langSwitch(i18n.Translations[state.conj], lang) if type(rec)=='table' then case = rec.case end end date1 = oneDatePhrase(date1, state.adj1, state.era1, state.units1, lang, 1, case[1], state) date2 = oneDatePhrase(date2, state.adj2, state.era2, state.units2, lang, 2, case[2], state) dateStr = translatePhrase(date1, date2, state.conj, lang, state) if era ~= '' then dateStr = translatePhrase(dateStr, '', era, lang, state) end return dateStr end local function otherPhrases(date1, date2, operation, era, lang, state) -- translate specialized phrases local dateStr = '' if operation == 'islamic' then if date2=='' then date2 = mw.getCurrentFrame():callParserFunction('#time', 'xmY', date1) end date1 = getISODate(date1, operation, lang, 1, nil) date2 = getISODate(date2, operation, lang, 2, nil) if era == '' then era = 'ad' end dateStr = translatePhrase(date1, '', era, lang, state) .. ' (' .. translatePhrase(date2, '', 'ah', lang, state) .. ')' era = '' elseif operation == 'julian' then if not date2 and date1 then -- Convert from Julian to Gregorian calendar date if Calendar == nil then Calendar = require("Module:Calendar") end local JDN = Calendar._date2jdn(date1, 0) if JDN then date2 = date1 -- first date is assumed to be Julian date1 = Calendar._jdn2date(JDN, 1) end end date1 = getISODate(date1, operation, lang, 1, nil) date2 = getISODate(date2, operation, lang, 2, nil) dateStr = translatePhrase(date1, date2, operation, lang, state) dateStr = mw.ustring.gsub(mw.ustring.gsub(dateStr, '%( ', '('), ' %)', ')') -- in case date2 is empty elseif operation == 'turn of the year' or operation == 'turn of the decade' or operation == 'turn of the century' then local dt = 1 if operation == 'turn of the decade' then dt=10 end if not date2 or date2=='' then date2=tostring(tonumber(date1)-dt) end if era~='bp' and era~='bc' then date1, date2 = date2, date1 end if operation == 'turn of the year' then date1 = ISOdate(date1, lang, '', '', 1) date2 = ISOdate(date2, lang, '', '', 1) else date1 = formatnum1(date1, lang) date2 = formatnum1(date2, lang) end dateStr = translatePhrase(date1, date2, operation, lang, state) elseif operation == 'year unknown' then dateStr = translatePhrase('', '', operation, lang, state) elseif operation == 'unknown' then dateStr = tostring(mw.message.new( "exif-unknowndate" ):inLanguage( lang )) end -- add era if era ~= '' then dateStr = translatePhrase(dateStr, '', era, lang, state) end return dateStr end local function checkAliases(str1, str2, sType) -- some inputs have many aliases - reconcile them and ensure string is playing a proper role local out = '' if str1 and str1~='' then local a = i18n.Synonyms[str1] -- look up synonyms of "str1" if a then out = a[1] else p.Error = string.format('<span style="background-color:red;">Error in [[Module:Complex date]]: %s is not recognized.</span>', str1) end elseif str2 and str2~='' then -- if "str1" of type "sType" is empty than maybe ... a = i18n.Synonyms[str2] -- ..."str2" is of the same type and is not empty if a and a[2]==sType then out = a[1] str2 = '' end end return out, str2 end local function datePrecision(dateStr, units) -- "in this module "Units" is a string like millennium, century, or decade -- "precision" is wikibase compatible date precision number: 6=millennium, 7=century, 8=decade, 9=year, 10=month, 11=day -- based on string or numeric input calculate "Units" and "precision" local dateNum = tonumber(dateStr) local precision if type(units)=='number' then precision = units if precision>11 then precision=11 end -- clip the range of precision values if precision==6 then units='millennium' elseif precision==7 then units='century' elseif precision==8 then units='decade' else units = '' end elseif type(units)=='string' then units = string.lower(units); if units=='millennium' then precision=6 elseif units=='century' then precision=7 elseif units=='decade' then precision=8 else precision=9 end end if units=='' or precision==9 then local sLen = mw.ustring.len(dateStr) if sLen<= 4 then precision=9 elseif sLen== 7 then precision=10 elseif sLen>=10 then precision=11 end units='' end if precision==6 and dateStr.match( dateStr, '%d000' )~=nil then dateStr = tostring(math.floor(tonumber(dateStr)/1000) +1) elseif precision==7 and mw.ustring.match( dateStr, '%d%d00' )~=nil then dateStr = tostring(math.floor(tonumber(dateStr)/100) +1) end return dateStr, units, precision end local function isodate2timestamp(dateStr, precision, era) -- convert date string to timestamps used by Quick Statements local tStamp = nil if era == 'ah' or precision<6 then return nil elseif era ~= '' then local eraLUT = {ad='+', bc='-', bp='-' } era = eraLUT[era] else era='+' end -- convert isodate to timestamp used by quick statements if precision>=9 then if string.match(dateStr,"^%d%d%d%d$") then -- if YYYY format tStamp = era .. dateStr .. '-00-00T00:00:00Z/9' elseif string.match(dateStr,"^%d%d%d%d%-%d%d$") then -- if YYYY-MM format tStamp = era .. dateStr .. '-00T00:00:00Z/10' elseif string.match(dateStr,"^%d%d%d%d%-%d%d%-%d%d$") then -- if YYYY-MM-DD format tStamp = era .. dateStr .. 'T00:00:00Z/11' end elseif precision==8 then -- decade tStamp = era .. dateStr .. '-00-00T00:00:00Z/8' elseif precision==7 then -- century local d = tostring(tonumber(dateStr)-1) tStamp = era .. d .. '50-00-00T00:00:00Z/7' elseif precision==6 then local d = tostring(tonumber(dateStr)-1) tStamp = era .. d .. '500-00-00T00:00:00Z/6' end return tStamp end local function oneDateQScode(dateStr, adj, era, precision) -- create QuickStatements string for "one date" dates local outputStr = '' local d = isodate2timestamp(dateStr, precision, era) if not d then return '' end local rLUT = { early='Q40719727' , mid='Q40719748', late='Q40719766', ['1quarter']='Q40690303' , ['2quarter']='Q40719649' , ['3quarter']='Q40719662', ['4quarter']='Q40719674', spring='Q40720559' , summer='Q40720564' , autumn='Q40720568' , winter='Q40720553', firsthalf='Q40719687', secondhalf='Q40719707' } local qLUT = {['from']='P580', ['until']='P582', ['after']='P1319', ['before']='P1326'} local refine = rLUT[adj] local qualitier = qLUT[adj] if adj=='' then outputStr = d elseif adj=='circa' then outputStr = d..",P1480,Q5727902" elseif refine then outputStr = d..",P4241,"..refine elseif precision>7 and qualitier then local century = string.gsub(d, 'Z%/%d+', 'Z/7') outputStr = century ..",".. qualitier ..","..d end return outputStr end local function twoDateQScode(date1, date2, state) -- create QuickStatements string for "two date" dates if state.adj1~='' or state.adj2~='' or state.era1~=state.era2 then return '' end local outputStr = '' local d1 = isodate2timestamp(date1, state.precision1, state.era1) local d2 = isodate2timestamp(date2, state.precision2, state.era2) if (not d1) or (not d2) then return '' end -- find date with lower precision in common to both dates local cd local year1 = string.sub(d1,2,5) local k = 0 for i = 1,10,1 do if string.sub(d1,1,i)==string.sub(d2,1,i) then k = i -- find last matching letter end end if k>=9 then -- same month, since "+YYYY-MM-" is in common cd = isodate2timestamp(string.sub(d1,2,8), 10, state.era1) elseif k>=6 and k<9 then -- same year, since "+YYYY-" is in common cd = isodate2timestamp(year1, 9, state.era1) elseif k==4 then -- same decade(k=4, precision=8), since "+YYY" is in common cd = isodate2timestamp(year1, 8, state.era1) elseif k==3 then -- same century(k=3, precision=7) since "+YY" is in common local d = tostring(math.floor(tonumber(year1)/100) +1) -- convert 1999 -> 20 cd = isodate2timestamp( d, 7, state.era1) elseif k==2 then -- same millennium (k=2, precision=6), since "+Y" is in common local d = tostring(math.floor(tonumber(year1)/1000) +1) -- convert 1999 -> 2 cd = isodate2timestamp( d, 6, state.era1) else return '' end --if not cd then -- return ' <br/>error: ' .. d1.." / " .. d2.." / ".. (cd or '') .." / ".. string.sub(d1,2,5).." / " .. string.sub(d2,2,5).." / " .. tostring(k) --end -- if state.conj=='from-until' then outputStr = cd ..",P580,".. d1 ..",P582,".. d2 elseif state.conj=='between' then outputStr = cd ..",P1319,".. d1 ..",P1326,".. d2 elseif state.conj=='circa2' then outputStr = cd ..",P1319,".. d1 ..",P1326,".. d2 ..",P1480,Q5727902" end return outputStr end -- ================================================== -- === External functions =========================== -- ================================================== function p.Era(frame) -- process inputs local dateStr local args = frame.args if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then args.lang = frame:callParserFunction( "int", "lang" ) -- get user's chosen language end local lang = args['lang'] local dateStr = args['date'] or '' local eraType = string.lower(args['era'] or '') dateStr = ISOdate(dateStr, lang, '', '', 1) if eraType then eraType = checkAliases(eraType ,'','e') dateStr = translatePhrase(dateStr, '', eraType, lang, {}) end return dateStr end function p._complex_date(conj, adj1, date1, units1, era1, adj2, date2, units2, era2, lang, passNr) local Output='' -- process inputs and save date in state array local state = {} state.conj = string.lower(conj or '') state.adj1 = string.lower(adj1 or '') state.adj2 = string.lower(adj2 or '') state.era1 = string.lower(era1 or '') state.era2 = string.lower(era2 or '') state.units1 = string.lower(units1 or '') state.units2 = string.lower(units2 or '') -- if date 1 is missing but date 2 is provided than swap them if date1 == '' and date2 ~= '' then date1 = date2 date2 = '' state = {adj1 = state.adj2, era1 = state.era2, units1 = state.units2, adj2 = '', era2 = '', units2 = '', conj=state.conj, num=1} end if date2 ~= '' then state.nDates = 2 elseif date1 ~= '' then state.nDates = 1 else state.nDates = 0 end -- reconcile alternative names for text inputs local conj = checkAliases(state.conj ,'' ,'j') state.adj1 ,conj = checkAliases(state.adj1 ,conj,'a') state.units1,conj = checkAliases(state.units1,conj,'p') state.era1 ,conj = checkAliases(state.era1 ,conj,'e') state.special,conj = checkAliases('',conj,'c') state.adj2 = checkAliases(state.adj2 ,'','a') state.units2 = checkAliases(state.units2,'','p') state.era2 = checkAliases(state.era2 ,'','e') state.conj = conj state.lang = lang if p.Error~=nil then return nil end -- calculate date precision value date1, state.units1, state.precision1 = datePrecision(date1, state.units1) date2, state.units2, state.precision2 = datePrecision(date2, state.units2) -- Handle special cases -- Some complex phrases can be created out of simpler ones. Therefore on pass # 1 we try to create -- the phrase using complex phrase and if that is not found than on the second pass we try to build -- the phrase out of the simpler ones if passNr==1 then if state.adj1=='circa' and state.nDates == 2 then state.conj = 'circa2' state.adj1 = '' state.adj2 = '' end if state.nDates == 2 and state.adj1=='late' and state.adj2=='early' and state.conj=='and' and state.units1==state.units2 and state.era1==state.era2 then if state.units1=='century' then state.conj='turn of the century' elseif state.units1=='decade' then state.conj='turn of the decade' elseif state.units1=='' then state.conj='turn of the year' end state.adj1 = '' state.adj2 = '' state.units1 = '' state.units2 = '' end end local errorStr = string.format( '\n*conj=%s, adj1=%s, era1=%s, unit1=%s, prec1=%i, adj2=%s, era2=%s, unit2=%s, prec2=%i, special=%s', state.conj, state.adj1, state.era1, state.units1, state.precision1, state.adj2, state.era2, state.units2, state.precision2, state.special) state.adj, state.era, state.units, state.precision = state.adj1, state.era1, state.units1, state.precision1 -- call specialized functions local QScode = '' if state.special~='' then Output = otherPhrases(date1, date2, state.special, state.era1, lang, state) elseif state.conj~='' then QScode = twoDateQScode(date1, date2, state) Output = twoDatePhrase(date1, date2, state, lang) elseif state.adj1~='' or state.era1~='' or state.units1~='' then Output = oneDatePhrase(date1, state.adj1, state.era1, state.units1, lang, 1, nil, state) QScode = oneDateQScode(date1, state.adj1, state.era1, state.precision1) elseif date1~='' then Output = ISOdate(date1, lang, '', 'dtstart', '100-999') end if p.Error~=nil then return errorStr end -- if there is any wikicode in the string than execute it if mw.ustring.find(Output, '{') then Output = mw.getCurrentFrame():preprocess(Output) end if QScode and #QScode>0 then QScode = ' <div style="display: none;">date QS:P,' .. QScode .. '</div>' end return Output .. QScode end function p.complex_date(frame) -- process inputs local dateStr, Error local args = frame.args if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then args.lang = frame:callParserFunction( "int", "lang" ) -- get user's chosen language end local date1 = args['date1'] or args['2'] or args['date'] or '' local date2 = args['date2'] or args['3'] or '' local conj = args['conj'] or args['1'] or '' local adj1 = args['adj1'] or args['adj'] or '' local adj2 = args['adj2'] or '' local units1 = args['precision1'] or args['precision'] or '' local units2 = args['precision2'] or args['precision'] or '' local era1 = args['era1'] or args['era'] or '' local era2 = args['era2'] or args['era'] or '' local lang = args['lang'] dateStr = p._complex_date(conj, adj1, date1, units1, era1, adj2, date2, units2, era2, lang, 1) if p.Error~=nil then dateStr = p.Error .. '[[Category:Pages using Complex date template with incorrect parameter]]' end return dateStr end return p ljyoask03usxmzzkva29k5qja8ngyvd 803632 803631 2020-05-15T03:56:09Z en>Johnuniq 0 fix one more global problem; couple of minor cleanups; this fixes errors in a few articles such as [[Alexey Verstovsky]] 803632 Scribunto text/plain --[[ __ __ _ _ ____ _ _ _ | \/ | ___ __| |_ _| | ___ _ / ___|___ _ __ ___ _ __ | | _____ __ __| | __ _| |_ ___ | |\/| |/ _ \ / _` | | | | |/ _ (_) | / _ \| '_ ` _ \| '_ \| |/ _ \ \/ / / _` |/ _` | __/ _ \ | | | | (_) | (_| | |_| | | __/_| |__| (_) | | | | | | |_) | | __/> < | (_| | (_| | || __/ |_| |_|\___/ \__,_|\__,_|_|\___(_)\____\___/|_| |_| |_| .__/|_|\___/_/\_\ \__,_|\__,_|\__\___| |_| This module is intended for creation of complex date phrases in variety of languages. Once deployed, please do not modify this code without applying the changes first at Module:Complex date/sandbox and testing at Module:Complex date/sandbox/testcases. Authors and maintainers: * User:Sn1per - first draft of the original version * User:Jarekt - corrections and expansion of the original version ]] -- List of external modules and functions local p = {Error = nil} local i18n = require('Module:i18n/complex date') local ISOdate = require('Module:ISOdate')._ISOdate local Calendar -- loaded lazily -- ================================================== -- === Internal functions =========================== -- ================================================== local function langSwitch(list,lang) local langList = mw.language.getFallbacksFor(lang) table.insert(langList,1,lang) table.insert(langList,math.max(#langList,2),'default') for i,language in ipairs(langList) do if list[language] then return list[language] end end end local function formatnum1(numStr, lang) -- mostly require('Module:Formatnum').formatNum function used to translate a number to use different numeral characters, -- except that it it does not call that function unless the language is on the list "LList" local LList = {bn=1,bpy=1,kn=1,hi=1,mr=1,new=1,pa=1,gu=1,fa=1,glk=1,mzn=1,ur=1,ar=1,ckb=1,ks=1,lo=1,['or']=1,bo=1,['ml-old']=1,mn=1,te=1,th=1} if LList[lang] then -- call only when the language is on the list numStr = require('Module:Formatnum').formatNum(numStr, lang, 1) end return numStr end local function getISODate(datestr, datetype, lang, num, case) -- translate dates in the format YYYY, YYYY-MM, and YYYY-MM-DD if not case and i18n.Translations[datetype] then -- look up the grammatical case needed and call ISOdate module local rec = langSwitch(i18n.Translations[datetype], lang) if type(rec)=='table' then case = rec.case[num] end end return ISOdate(datestr, lang, case, '', 1) end local function translatePhrase(date1, date2, operation, lang, state) -- use tables in Module:i18n/complex date to translate a phrase if not i18n.Translations[operation] then p.Error = string.format('<span style="background-color:red;">Error in [[Module:Complex date]]: input parameter "%s" is not recognized.</span>', operation or 'nil') return '' end local dateStr = langSwitch(i18n.Translations[operation], lang) if type(dateStr)=='table' then dateStr = dateStr[1] end if type(dateStr)=='function' then local success local nDates = i18n.Translations[operation]['nDates'] if nDates==2 then -- 2 date phrase success, dateStr = pcall(dateStr, date1, date2, state) else -- 1 date phrase success, dateStr = pcall(dateStr, date1, state) end end if type(dateStr)=='string' then -- replace parts of the string '$date1' and '$date2' with date1 and date2 strings dateStr = mw.ustring.gsub(dateStr, '$date1', date1) dateStr = mw.ustring.gsub(dateStr, '$date2', date2) else -- Special case of more complex phrases that can be build out of simple phrases -- If complex case is not translated to "lang" than build it out of simpler ones local x = dateStr dateStr = p._complex_date(x.conj, x.adj1, date1, x.units1, x.era1, x.adj2, date2, x.units2, x.era2, lang, 2) end return dateStr end local function oneDatePhrase(dateStr, adj, era, units, lang, num, case, state) -- translate a single date phrase if num==2 then state.adj, state.era, state.units, state.precision = state.adj2, state.era2, state.units2, state.precision2 end -- dateStr can have many forms: ISO date, year or a number for -- decade, century or millennium if units == '' then -- unit is "year", "month", "day" dateStr = getISODate(dateStr, adj, lang, num, case) else -- units is "decade", "century", "millennium'' dateStr = translatePhrase(dateStr, '', units, lang, state) end -- add adjective ("early", "mid", etc.) or preposition ("before", "after", -- "circa", etc.) to the date if adj ~= '' then dateStr = translatePhrase(dateStr, '', adj, lang, state) else -- only era? dateStr = formatnum1(dateStr, lang) end -- add era if era ~= '' then dateStr = translatePhrase(dateStr, '', era, lang, state) end return dateStr end local function twoDatePhrase(date1, date2, state, lang) -- translate a double date phrase local dateStr, case local era='' if state.era1 == state.era2 then -- if both eras are the same than add it only once era = state.era1 state.era1 = '' state.era2 = '' end case = {nil, nil} if i18n.Translations[state.conj] then local rec = langSwitch(i18n.Translations[state.conj], lang) if type(rec)=='table' then case = rec.case end end date1 = oneDatePhrase(date1, state.adj1, state.era1, state.units1, lang, 1, case[1], state) date2 = oneDatePhrase(date2, state.adj2, state.era2, state.units2, lang, 2, case[2], state) dateStr = translatePhrase(date1, date2, state.conj, lang, state) if era ~= '' then dateStr = translatePhrase(dateStr, '', era, lang, state) end return dateStr end local function otherPhrases(date1, date2, operation, era, lang, state) -- translate specialized phrases local dateStr = '' if operation == 'islamic' then if date2=='' then date2 = mw.getCurrentFrame():callParserFunction('#time', 'xmY', date1) end date1 = getISODate(date1, operation, lang, 1, nil) date2 = getISODate(date2, operation, lang, 2, nil) if era == '' then era = 'ad' end dateStr = translatePhrase(date1, '', era, lang, state) .. ' (' .. translatePhrase(date2, '', 'ah', lang, state) .. ')' era = '' elseif operation == 'julian' then if not date2 and date1 then -- Convert from Julian to Gregorian calendar date if Calendar == nil then Calendar = require("Module:Calendar") end local JDN = Calendar._date2jdn(date1, 0) if JDN then date2 = date1 -- first date is assumed to be Julian date1 = Calendar._jdn2date(JDN, 1) end end date1 = getISODate(date1, operation, lang, 1, nil) date2 = getISODate(date2, operation, lang, 2, nil) dateStr = translatePhrase(date1, date2, operation, lang, state) dateStr = mw.ustring.gsub(mw.ustring.gsub(dateStr, '%( ', '('), ' %)', ')') -- in case date2 is empty elseif operation == 'turn of the year' or operation == 'turn of the decade' or operation == 'turn of the century' then local dt = 1 if operation == 'turn of the decade' then dt=10 end if not date2 or date2=='' then date2=tostring(tonumber(date1)-dt) end if era~='bp' and era~='bc' then date1, date2 = date2, date1 end if operation == 'turn of the year' then date1 = ISOdate(date1, lang, '', '', 1) date2 = ISOdate(date2, lang, '', '', 1) else date1 = formatnum1(date1, lang) date2 = formatnum1(date2, lang) end dateStr = translatePhrase(date1, date2, operation, lang, state) elseif operation == 'year unknown' then dateStr = translatePhrase('', '', operation, lang, state) elseif operation == 'unknown' then dateStr = tostring(mw.message.new( "exif-unknowndate" ):inLanguage( lang )) end -- add era if era ~= '' then dateStr = translatePhrase(dateStr, '', era, lang, state) end return dateStr end local function checkAliases(str1, str2, sType) -- some inputs have many aliases - reconcile them and ensure string is playing a proper role local out = '' if str1 and str1~='' then local a = i18n.Synonyms[str1] -- look up synonyms of "str1" if a then out = a[1] else p.Error = string.format('<span style="background-color:red;">Error in [[Module:Complex date]]: %s is not recognized.</span>', str1) end elseif str2 and str2~='' then -- if "str1" of type "sType" is empty than maybe ... local a = i18n.Synonyms[str2] -- ..."str2" is of the same type and is not empty if a and a[2]==sType then out = a[1] str2 = '' end end return out, str2 end local function datePrecision(dateStr, units) -- "in this module "Units" is a string like millennium, century, or decade -- "precision" is wikibase compatible date precision number: 6=millennium, 7=century, 8=decade, 9=year, 10=month, 11=day -- based on string or numeric input calculate "Units" and "precision" local dateNum = tonumber(dateStr) -- TODO dateNum is not used: should remove? local precision if type(units)=='number' then precision = units if precision>11 then precision=11 end -- clip the range of precision values if precision==6 then units='millennium' elseif precision==7 then units='century' elseif precision==8 then units='decade' else units = '' end elseif type(units)=='string' then units = string.lower(units); if units=='millennium' then precision=6 elseif units=='century' then precision=7 elseif units=='decade' then precision=8 else precision=9 end end if units=='' or precision==9 then local sLen = mw.ustring.len(dateStr) if sLen<= 4 then precision=9 elseif sLen== 7 then precision=10 elseif sLen>=10 then precision=11 end units='' end if precision==6 and dateStr.match( dateStr, '%d000' )~=nil then dateStr = tostring(math.floor(tonumber(dateStr)/1000) +1) elseif precision==7 and mw.ustring.match( dateStr, '%d%d00' )~=nil then dateStr = tostring(math.floor(tonumber(dateStr)/100) +1) end return dateStr, units, precision end local function isodate2timestamp(dateStr, precision, era) -- convert date string to timestamps used by Quick Statements local tStamp = nil if era == 'ah' or precision<6 then return nil elseif era ~= '' then local eraLUT = {ad='+', bc='-', bp='-' } era = eraLUT[era] else era='+' end -- convert isodate to timestamp used by quick statements if precision>=9 then if string.match(dateStr,"^%d%d%d%d$") then -- if YYYY format tStamp = era .. dateStr .. '-00-00T00:00:00Z/9' elseif string.match(dateStr,"^%d%d%d%d%-%d%d$") then -- if YYYY-MM format tStamp = era .. dateStr .. '-00T00:00:00Z/10' elseif string.match(dateStr,"^%d%d%d%d%-%d%d%-%d%d$") then -- if YYYY-MM-DD format tStamp = era .. dateStr .. 'T00:00:00Z/11' end elseif precision==8 then -- decade tStamp = era .. dateStr .. '-00-00T00:00:00Z/8' elseif precision==7 then -- century local d = tostring(tonumber(dateStr)-1) tStamp = era .. d .. '50-00-00T00:00:00Z/7' elseif precision==6 then local d = tostring(tonumber(dateStr)-1) tStamp = era .. d .. '500-00-00T00:00:00Z/6' end return tStamp end local function oneDateQScode(dateStr, adj, era, precision) -- create QuickStatements string for "one date" dates local outputStr = '' local d = isodate2timestamp(dateStr, precision, era) if not d then return '' end local rLUT = { early='Q40719727' , mid='Q40719748', late='Q40719766', ['1quarter']='Q40690303' , ['2quarter']='Q40719649' , ['3quarter']='Q40719662', ['4quarter']='Q40719674', spring='Q40720559' , summer='Q40720564' , autumn='Q40720568' , winter='Q40720553', firsthalf='Q40719687', secondhalf='Q40719707' } local qLUT = {['from']='P580', ['until']='P582', ['after']='P1319', ['before']='P1326'} local refine = rLUT[adj] local qualitier = qLUT[adj] if adj=='' then outputStr = d elseif adj=='circa' then outputStr = d..",P1480,Q5727902" elseif refine then outputStr = d..",P4241,"..refine elseif precision>7 and qualitier then local century = string.gsub(d, 'Z%/%d+', 'Z/7') outputStr = century ..",".. qualitier ..","..d end return outputStr end local function twoDateQScode(date1, date2, state) -- create QuickStatements string for "two date" dates if state.adj1~='' or state.adj2~='' or state.era1~=state.era2 then return '' end local outputStr = '' local d1 = isodate2timestamp(date1, state.precision1, state.era1) local d2 = isodate2timestamp(date2, state.precision2, state.era2) if (not d1) or (not d2) then return '' end -- find date with lower precision in common to both dates local cd local year1 = string.sub(d1,2,5) local k = 0 for i = 1,10,1 do if string.sub(d1,1,i)==string.sub(d2,1,i) then k = i -- find last matching letter end end if k>=9 then -- same month, since "+YYYY-MM-" is in common cd = isodate2timestamp(string.sub(d1,2,8), 10, state.era1) elseif k>=6 and k<9 then -- same year, since "+YYYY-" is in common cd = isodate2timestamp(year1, 9, state.era1) elseif k==4 then -- same decade(k=4, precision=8), since "+YYY" is in common cd = isodate2timestamp(year1, 8, state.era1) elseif k==3 then -- same century(k=3, precision=7) since "+YY" is in common local d = tostring(math.floor(tonumber(year1)/100) +1) -- convert 1999 -> 20 cd = isodate2timestamp( d, 7, state.era1) elseif k==2 then -- same millennium (k=2, precision=6), since "+Y" is in common local d = tostring(math.floor(tonumber(year1)/1000) +1) -- convert 1999 -> 2 cd = isodate2timestamp( d, 6, state.era1) else return '' end --if not cd then -- return ' <br/>error: ' .. d1.." / " .. d2.." / ".. (cd or '') .." / ".. string.sub(d1,2,5).." / " .. string.sub(d2,2,5).." / " .. tostring(k) --end -- if state.conj=='from-until' then outputStr = cd ..",P580,".. d1 ..",P582,".. d2 elseif state.conj=='between' then outputStr = cd ..",P1319,".. d1 ..",P1326,".. d2 elseif state.conj=='circa2' then outputStr = cd ..",P1319,".. d1 ..",P1326,".. d2 ..",P1480,Q5727902" end return outputStr end -- ================================================== -- === External functions =========================== -- ================================================== function p.Era(frame) -- process inputs local args = frame.args if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then args.lang = frame:callParserFunction( "int", "lang" ) -- get user's chosen language end local lang = args['lang'] local dateStr = args['date'] or '' local eraType = string.lower(args['era'] or '') dateStr = ISOdate(dateStr, lang, '', '', 1) if eraType then eraType = checkAliases(eraType ,'','e') dateStr = translatePhrase(dateStr, '', eraType, lang, {}) end return dateStr end function p._complex_date(conj, adj1, date1, units1, era1, adj2, date2, units2, era2, lang, passNr) local Output='' -- process inputs and save date in state array local state = {} state.conj = string.lower(conj or '') state.adj1 = string.lower(adj1 or '') state.adj2 = string.lower(adj2 or '') state.era1 = string.lower(era1 or '') state.era2 = string.lower(era2 or '') state.units1 = string.lower(units1 or '') state.units2 = string.lower(units2 or '') -- if date 1 is missing but date 2 is provided than swap them if date1 == '' and date2 ~= '' then date1 = date2 date2 = '' state = {adj1 = state.adj2, era1 = state.era2, units1 = state.units2, adj2 = '', era2 = '', units2 = '', conj=state.conj, num=1} end if date2 ~= '' then state.nDates = 2 elseif date1 ~= '' then state.nDates = 1 else state.nDates = 0 end -- reconcile alternative names for text inputs local conj = checkAliases(state.conj ,'' ,'j') state.adj1 ,conj = checkAliases(state.adj1 ,conj,'a') state.units1,conj = checkAliases(state.units1,conj,'p') state.era1 ,conj = checkAliases(state.era1 ,conj,'e') state.special,conj = checkAliases('',conj,'c') state.adj2 = checkAliases(state.adj2 ,'','a') state.units2 = checkAliases(state.units2,'','p') state.era2 = checkAliases(state.era2 ,'','e') state.conj = conj state.lang = lang if p.Error~=nil then return nil end -- calculate date precision value date1, state.units1, state.precision1 = datePrecision(date1, state.units1) date2, state.units2, state.precision2 = datePrecision(date2, state.units2) -- Handle special cases -- Some complex phrases can be created out of simpler ones. Therefore on pass # 1 we try to create -- the phrase using complex phrase and if that is not found than on the second pass we try to build -- the phrase out of the simpler ones if passNr==1 then if state.adj1=='circa' and state.nDates == 2 then state.conj = 'circa2' state.adj1 = '' state.adj2 = '' end if state.nDates == 2 and state.adj1=='late' and state.adj2=='early' and state.conj=='and' and state.units1==state.units2 and state.era1==state.era2 then if state.units1=='century' then state.conj='turn of the century' elseif state.units1=='decade' then state.conj='turn of the decade' elseif state.units1=='' then state.conj='turn of the year' end state.adj1 = '' state.adj2 = '' state.units1 = '' state.units2 = '' end end local errorStr = string.format( '\n*conj=%s, adj1=%s, era1=%s, unit1=%s, prec1=%i, adj2=%s, era2=%s, unit2=%s, prec2=%i, special=%s', state.conj, state.adj1, state.era1, state.units1, state.precision1, state.adj2, state.era2, state.units2, state.precision2, state.special) state.adj, state.era, state.units, state.precision = state.adj1, state.era1, state.units1, state.precision1 -- call specialized functions local QScode = '' if state.special~='' then Output = otherPhrases(date1, date2, state.special, state.era1, lang, state) elseif state.conj~='' then QScode = twoDateQScode(date1, date2, state) Output = twoDatePhrase(date1, date2, state, lang) elseif state.adj1~='' or state.era1~='' or state.units1~='' then Output = oneDatePhrase(date1, state.adj1, state.era1, state.units1, lang, 1, nil, state) QScode = oneDateQScode(date1, state.adj1, state.era1, state.precision1) elseif date1~='' then Output = ISOdate(date1, lang, '', 'dtstart', '100-999') end if p.Error~=nil then return errorStr end -- if there is any wikicode in the string than execute it if mw.ustring.find(Output, '{') then Output = mw.getCurrentFrame():preprocess(Output) end if QScode and #QScode>0 then QScode = ' <div style="display: none;">date QS:P,' .. QScode .. '</div>' end return Output .. QScode end function p.complex_date(frame) -- process inputs local dateStr local args = frame.args if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then args.lang = frame:callParserFunction( "int", "lang" ) -- get user's chosen language end local date1 = args['date1'] or args['2'] or args['date'] or '' local date2 = args['date2'] or args['3'] or '' local conj = args['conj'] or args['1'] or '' local adj1 = args['adj1'] or args['adj'] or '' local adj2 = args['adj2'] or '' local units1 = args['precision1'] or args['precision'] or '' local units2 = args['precision2'] or args['precision'] or '' local era1 = args['era1'] or args['era'] or '' local era2 = args['era2'] or args['era'] or '' local lang = args['lang'] dateStr = p._complex_date(conj, adj1, date1, units1, era1, adj2, date2, units2, era2, lang, 1) if p.Error~=nil then dateStr = p.Error .. '[[Category:Pages using Complex date template with incorrect parameter]]' end return dateStr end return p fhn8afhakgi5qa5gxem0fsfqnz0xl2s 803633 803632 2020-05-15T19:21:05Z en>RexxS 0 update from sandbox (commons version) 803633 Scribunto text/plain --[[ __ __ _ _ ____ _ _ | \/ | ___ __| |_ _| | ___ _ / ___|___ _ __ ___ _ __ | | _____ __ __| | __ _| |_ ___ | |\/| |/ _ \ / _` | | | | |/ _ (_) | / _ \| '_ ` _ \| '_ \| |/ _ \ \/ / / _` |/ _` | __/ _ \ | | | | (_) | (_| | |_| | | __/_| |__| (_) | | | | | | |_) | | __/> < | (_| | (_| | || __/ |_| |_|\___/ \__,_|\__,_|_|\___(_)\____\___/|_| |_| |_| .__/|_|\___/_/\_\ \__,_|\__,_|\__\___| |_| This module is intended for creation of complex date phrases in variety of languages. Once deployed, please do not modify this code without applying the changes first at Module:Complex date/sandbox and testing at Module:Complex date/sandbox/testcases. Authors and maintainers: * User:Sn1per - first draft of the original version * User:Jarekt - corrections and expansion of the original version ]] -- List of external modules and functions local p = {Error = nil} local i18n = require('Module:i18n/complex date') -- used for translations of date related phrases local ISOdate = require('Module:ISOdate')._ISOdate -- used for parsing dates in YYYY-MM-DD and related formats local Calendar -- loaded lazily -- ================================================== -- === Internal functions =========================== -- ================================================== local function langSwitch(list,lang) local langList = mw.language.getFallbacksFor(lang) table.insert(langList,1,lang) table.insert(langList,math.max(#langList,2),'default') for i,language in ipairs(langList) do if list[language] then return list[language] end end end -- ================================================== local function formatnum1(numStr, lang) -- mostly require('Module:Formatnum').formatNum function used to translate a number to use different numeral characters, -- except that it it does not call that function unless the language is on the list "LList" local LList = {bn=1,bpy=1,kn=1,hi=1,mr=1,new=1,pa=1,gu=1,fa=1,glk=1,mzn=1,ur=1,ar=1,ckb=1,ks=1,lo=1,['or']=1,bo=1,['ml-old']=1,mn=1,te=1,th=1} if LList[lang] then -- call only when the language is on the list numStr = require('Module:Formatnum').formatNum(numStr, lang, 1) end return numStr end -- ================================================== local function getISODate(datestr, datetype, lang, num, case) -- translate dates in the format YYYY, YYYY-MM, and YYYY-MM-DD if not case and i18n.Translations[datetype] then -- look up the grammatical case needed and call ISOdate module local rec = langSwitch(i18n.Translations[datetype], lang) if type(rec)=='table' then case = rec.case[num] end end return ISOdate(datestr, lang, case, '', 1) end -- ======================================================================= local function translatePhrase(date1, date2, operation, lang, state) -- use tables in Module:i18n/complex date to translate a phrase if not i18n.Translations[operation] then p.Error = string.format('<span style="background-color:red;">Error in [[Module:Complex date]]: input parameter "%s" is not recognized.</span>', operation or 'nil') return '' end local dateStr = langSwitch(i18n.Translations[operation], lang) if type(dateStr)=='table' then dateStr = dateStr[1] end if type(dateStr)=='function' then local dateFunc = dateStr local nDates = i18n.Translations[operation]['nDates'] if nDates==2 then -- 2 date phrase dateStr = dateFunc(date1, date2, state) else -- 1 date phrase dateStr = dateFunc(date1, state) end end if type(dateStr)=='string' then -- replace parts of the string '$date1' and '$date2' with date1 and date2 strings dateStr = mw.ustring.gsub(dateStr, '$date1', date1) dateStr = mw.ustring.gsub(dateStr, '$date2', date2) else -- Special case of more complex phrases that can be build out of simple phrases -- If complex case is not translated to "lang" than build it out of simpler ones local x = dateStr dateStr = p._complex_date(x.conj, x.adj1, date1, x.units1, x.era1, x.adj2, date2, x.units2, x.era2, lang, 2) end return dateStr end -- ======================================================================= local function oneDatePhrase(dateStr, adj, era, units, lang, num, case, state) -- translate a single date phrase if num==2 then state.adj, state.era, state.units, state.precision = state.adj2, state.era2, state.units2, state.precision2 end -- dateStr can have many forms: ISO date, year or a number for -- decade, century or millennium if units == '' then -- unit is "year", "month", "day" dateStr = getISODate(dateStr, adj, lang, num, case) else -- units is "decade", "century", "millennium'' dateStr = translatePhrase(dateStr, '', units, lang, state) end -- add adjective ("early", "mid", etc.) or preposition ("before", "after", -- "circa", etc.) to the date if adj ~= '' then dateStr = translatePhrase(dateStr, '', adj, lang, state) else -- only era? dateStr = formatnum1(dateStr, lang) end -- add era if era ~= '' then dateStr = translatePhrase(dateStr, '', era, lang, state) end return dateStr end -- ======================================================================= local function twoDatePhrase(date1, date2, state, lang) -- translate a double date phrase local dateStr, case local era='' if state.era1 == state.era2 then -- if both eras are the same than add it only once era = state.era1 state.era1 = '' state.era2 = '' end case = {nil, nil} if i18n.Translations[state.conj] then local rec = langSwitch(i18n.Translations[state.conj], lang) if type(rec)=='table' then case = rec.case end end date1 = oneDatePhrase(date1, state.adj1, state.era1, state.units1, lang, 1, case[1], state) date2 = oneDatePhrase(date2, state.adj2, state.era2, state.units2, lang, 2, case[2], state) dateStr = translatePhrase(date1, date2, state.conj, lang, state) if era ~= '' then dateStr = translatePhrase(dateStr, '', era, lang, state) end return dateStr end -- ======================================================================= local function otherPhrases(date1, date2, operation, era, lang, state) -- translate specialized phrases local dateStr = '' if operation == 'islamic' then if date2=='' then date2 = mw.getCurrentFrame():callParserFunction('#time', 'xmY', date1) end date1 = getISODate(date1, operation, lang, 1, nil) date2 = getISODate(date2, operation, lang, 2, nil) if era == '' then era = 'ad' end dateStr = translatePhrase(date1, '', era, lang, state) .. ' (' .. translatePhrase(date2, '', 'ah', lang, state) .. ')' era = '' elseif operation == 'julian' then if not date2 and date1 then -- Convert from Julian to Gregorian calendar date if Calendar == nil then Calendar = require("Module:Calendar") -- lazy loding (only if needed) end local JDN = Calendar._date2jdn(date1, 0) if JDN then date2 = date1 -- first date is assumed to be Julian date1 = Calendar._jdn2date(JDN, 1) end end date1 = getISODate(date1, operation, lang, 1, nil) date2 = getISODate(date2, operation, lang, 2, nil) dateStr = translatePhrase(date1, date2, operation, lang, state) dateStr = mw.ustring.gsub(mw.ustring.gsub(dateStr, '%( ', '('), ' %)', ')') -- in case date2 is empty elseif operation == 'turn of the year' or operation == 'turn of the decade' or operation == 'turn of the century' then local dt = 1 if operation == 'turn of the decade' then dt=10 end if not date2 or date2=='' then date2=tostring(tonumber(date1)-dt) end if era~='bp' and era~='bc' then date1, date2 = date2, date1 end if operation == 'turn of the year' then date1 = ISOdate(date1, lang, '', '', 1) date2 = ISOdate(date2, lang, '', '', 1) else date1 = formatnum1(date1, lang) date2 = formatnum1(date2, lang) end dateStr = translatePhrase(date1, date2, operation, lang, state) elseif operation == 'year unknown' then dateStr = translatePhrase('', '', operation, lang, state) .. '<div style="display: none;">Unknown date</div>' elseif operation == 'unknown' then dateStr = tostring(mw.message.new( "exif-unknowndate" ):inLanguage( lang )) .. '<div style="display: none;">Unknown date</div>' end -- add era if era ~= '' then dateStr = translatePhrase(dateStr, '', era, lang, state) end return dateStr end -- ======================================================================= local function checkAliases(str1, str2, sType) -- some inputs have many aliases - reconcile them and ensure string is playing a proper role local out = '' if str1 and str1~='' then local a = i18n.Synonyms[str1] -- look up synonyms of "str1" if a then out = a[1] else p.Error = string.format('<span style="background-color:red;">Error in [[Module:Complex date]]: %s is not recognized.</span>', str1) end elseif str2 and str2~='' then -- if "str1" of type "sType" is empty than maybe ... local a = i18n.Synonyms[str2] -- ..."str2" is of the same type and is not empty if a and a[2]==sType then out = a[1] str2 = '' end end return out, str2 end -- ======================================================================= local function datePrecision(dateStr, units) -- "in this module "Units" is a string like millennium, century, or decade -- "precision" is wikibase compatible date precision number: 6=millennium, 7=century, 8=decade, 9=year, 10=month, 11=day -- based on string or numeric input calculate "Units" and "precision" local precision if type(units)=='number' then precision = units if precision>11 then precision=11 end -- clip the range of precision values if precision==6 then units='millennium' elseif precision==7 then units='century' elseif precision==8 then units='decade' else units = '' end elseif type(units)=='string' then units = string.lower(units) if units=='millennium' then precision=6 elseif units=='century' then precision=7 elseif units=='decade' then precision=8 else precision=9 end end if units=='' or precision==9 then local sLen = mw.ustring.len(dateStr) if sLen<= 4 then precision=9 elseif sLen== 7 then precision=10 elseif sLen>=10 then precision=11 end units='' end if precision==6 and dateStr.match( dateStr, '%d000' )~=nil then dateStr = tostring(math.floor(tonumber(dateStr)/1000) +1) elseif precision==7 and mw.ustring.match( dateStr, '%d%d00' )~=nil then dateStr = tostring(math.floor(tonumber(dateStr)/100) +1) end return dateStr, units, precision end -- ======================================================================= local function isodate2timestamp(dateStr, precision, era) -- convert date string to timestamps used by Quick Statements local tStamp = nil if era == 'ah' or precision<6 then return nil elseif era ~= '' then local eraLUT = {ad='+', bc='-', bp='-' } era = eraLUT[era] else era='+' end -- convert isodate to timestamp used by quick statements if precision>=9 then if string.match(dateStr,"^%d%d%d%d$") then -- if YYYY format tStamp = era .. dateStr .. '-00-00T00:00:00Z/9' elseif string.match(dateStr,"^%d%d%d%d%-%d%d$") then -- if YYYY-MM format tStamp = era .. dateStr .. '-00T00:00:00Z/10' elseif string.match(dateStr,"^%d%d%d%d%-%d%d%-%d%d$") then -- if YYYY-MM-DD format tStamp = era .. dateStr .. 'T00:00:00Z/11' end elseif precision==8 then -- decade tStamp = era .. dateStr .. '-00-00T00:00:00Z/8' elseif precision==7 then -- century local d = tostring(tonumber(dateStr)-1) tStamp = era .. d .. '50-00-00T00:00:00Z/7' elseif precision==6 then local d = tostring(tonumber(dateStr)-1) tStamp = era .. d .. '500-00-00T00:00:00Z/6' end return tStamp end -- ======================================================================= local function oneDateQScode(dateStr, adj, era, precision) -- create QuickStatements string for "one date" dates local outputStr = '' local d = isodate2timestamp(dateStr, precision, era) if not d then return '' end local rLUT = { early='Q40719727' , mid='Q40719748', late='Q40719766', ['1quarter']='Q40690303' , ['2quarter']='Q40719649' , ['3quarter']='Q40719662', ['4quarter']='Q40719674', spring='Q40720559' , summer='Q40720564' , autumn='Q40720568' , winter='Q40720553', firsthalf='Q40719687', secondhalf='Q40719707' } local qLUT = {['from']='P580', ['until']='P582', ['after']='P1319', ['before']='P1326', ['by']='P1326'} local refine = rLUT[adj] local qualitier = qLUT[adj] if adj=='' then outputStr = d elseif adj=='circa' then outputStr = d..",P1480,Q5727902" elseif refine then outputStr = d..",P4241,"..refine elseif precision>7 and qualitier then local century = string.gsub(d, 'Z%/%d+', 'Z/7') outputStr = century ..",".. qualitier ..","..d end return outputStr end -- ======================================================================= local function twoDateQScode(date1, date2, state) -- create QuickStatements string for "two date" dates if state.adj1~='' or state.adj2~='' or state.era1~=state.era2 then return '' -- QuickStatements string are not generated for two date phrases with adjectives end local outputStr = '' local d1 = isodate2timestamp(date1, state.precision1, state.era1) local d2 = isodate2timestamp(date2, state.precision2, state.era2) if (not d1) or (not d2) then return '' end -- find date with lower precision in common to both dates local cd local year1 = tonumber(string.sub(d1,2,5)) local year2 = tonumber(string.sub(d2,2,5)) local k = 0 for i = 1,10,1 do if string.sub(d1,1,i)==string.sub(d2,1,i) then k = i -- find last matching letter end end if k>=9 then -- same month, since "+YYYY-MM-" is in common cd = isodate2timestamp(string.sub(d1,2,8), 10, state.era1) elseif k>=6 and k<9 then -- same year, since "+YYYY-" is in common cd = isodate2timestamp(tostring(year1), 9, state.era1) elseif k==4 then -- same decade(k=4, precision=8), since "+YYY" is in common cd = isodate2timestamp(tostring(year1), 8, state.era1) elseif k==3 then -- same century(k=3, precision=7) since "+YY" is in common local d = tostring(math.floor(year1/100) +1) -- convert 1999 -> 20 cd = isodate2timestamp( d, 7, state.era1) elseif k==2 then -- same millennium (k=2, precision=6), since "+Y" is in common local d = tostring(math.floor(year1/1000) +1) -- convert 1999 -> 2 cd = isodate2timestamp( d, 6, state.era1) end if not cd then return '' end --if not cd then -- return ' <br/>error: ' .. d1.." / " .. d2.." / ".. (cd or '') .." / ".. string.sub(d1,2,5).." / " .. string.sub(d2,2,5).." / " .. tostring(k) --end -- if (state.conj=='from-until') or (state.conj=='and' and year1==year2-1) then outputStr = cd ..",P580,".. d1 ..",P582,".. d2 elseif (state.conj=='between') or (state.conj=='or' and year1==year2-1) then outputStr = cd ..",P1319,".. d1 ..",P1326,".. d2 elseif state.conj=='circa2' then outputStr = cd ..",P1319,".. d1 ..",P1326,".. d2 ..",P1480,Q5727902" end return outputStr end -- ======================================================================= local function processInputParams(conj, adj1, date1, units1, era1, adj2, date2, units2, era2, lang, passNr) -- process inputs and save date in state array local state = {} state.conj = string.lower(conj or '') state.adj1 = string.lower(adj1 or '') state.adj2 = string.lower(adj2 or '') state.era1 = string.lower(era1 or '') state.era2 = string.lower(era2 or '') state.units1 = string.lower(units1 or '') state.units2 = string.lower(units2 or '') -- if date 1 is missing but date 2 is provided than swap them if date1 == '' and date2 ~= '' then date1 = date2 date2 = '' state = {adj1 = state.adj2, era1 = state.era2, units1 = state.units2, adj2 = '', era2 = '', units2 = '', conj=state.conj, num=1} end if date2 ~= '' then state.nDates = 2 elseif date1 ~= '' then state.nDates = 1 else state.nDates = 0 end -- reconcile alternative names for text inputs local conj = checkAliases(state.conj ,'' ,'j') state.adj1 ,conj = checkAliases(state.adj1 ,conj,'a') state.units1,conj = checkAliases(state.units1,conj,'p') state.era1 ,conj = checkAliases(state.era1 ,conj,'e') state.special,conj = checkAliases('',conj,'c') state.adj2 = checkAliases(state.adj2 ,'','a') state.units2 = checkAliases(state.units2,'','p') state.era2 = checkAliases(state.era2 ,'','e') state.conj = conj state.lang = lang if p.Error~=nil then return nil end -- calculate date precision value date1, state.units1, state.precision1 = datePrecision(date1, state.units1) date2, state.units2, state.precision2 = datePrecision(date2, state.units2) -- Handle special cases -- Some complex phrases can be created out of simpler ones. Therefore on pass # 1 we try to create -- the phrase using complex phrase and if that is not found than on the second pass we try to build -- the phrase out of the simpler ones if passNr==1 then if state.adj1=='circa' and state.nDates == 2 then state.conj = 'circa2' state.adj1 = '' state.adj2 = '' end if state.nDates == 2 and state.adj1=='late' and state.adj2=='early' and state.conj=='and' and state.units1==state.units2 and state.era1==state.era2 then if state.units1=='century' then state.conj='turn of the century' elseif state.units1=='decade' then state.conj='turn of the decade' elseif state.units1=='' then state.conj='turn of the year' end state.adj1 = '' state.adj2 = '' state.units1 = '' state.units2 = '' end end state.adj, state.era, state.units, state.precision = state.adj1, state.era1, state.units1, state.precision1 return date1, date2, state end -- ================================================== -- === External functions =========================== -- ================================================== function p.Era(frame) -- process inputs local dateStr local args = frame.args if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then args.lang = frame:callParserFunction( "int", "lang" ) -- get user's chosen language end local lang = args['lang'] local dateStr = args['date'] or '' local eraType = string.lower(args['era'] or '') dateStr = ISOdate(dateStr, lang, '', '', 1) if eraType then eraType = checkAliases(eraType ,'','e') dateStr = translatePhrase(dateStr, '', eraType, lang, {}) end return dateStr end -- ======================================================================= function p._complex_date(conj, adj1, date1, units1, era1, adj2, date2, units2, era2, lang, passNr) local Output='' local state -- process inputs and save date in state array date1, date2, state = processInputParams(conj, adj1, date1, units1, era1, adj2, date2, units2, era2, lang, passNr) if p.Error~=nil then return nil end local errorStr = string.format( '\n*conj=%s, adj1=%s, era1=%s, unit1=%s, prec1=%i, adj2=%s, era2=%s, unit2=%s, prec2=%i, special=%s', state.conj, state.adj1, state.era1, state.units1, state.precision1, state.adj2, state.era2, state.units2, state.precision2, state.special) -- call specialized functions local QScode = '' if state.special~='' then Output = otherPhrases(date1, date2, state.special, state.era1, lang, state) elseif state.conj~='' then QScode = twoDateQScode(date1, date2, state) Output = twoDatePhrase(date1, date2, state, lang) elseif state.adj1~='' or state.era1~='' or state.units1~='' then Output = oneDatePhrase(date1, state.adj1, state.era1, state.units1, lang, 1, nil, state) QScode = oneDateQScode(date1, state.adj1, state.era1, state.precision1) elseif date1~='' then Output = ISOdate(date1, lang, '', 'dtstart', '100-999') end if p.Error~=nil then return errorStr end -- if there is any wikicode in the string than execute it if mw.ustring.find(Output, '{') then Output = mw.getCurrentFrame():preprocess(Output) end if QScode and #QScode>0 then QScode = ' <div style="display: none;">date QS:P,' .. QScode .. '</div>' end return Output .. QScode end -- ======================================================================= function p._complex_date_cer(conj, adj1, date1, units1, era1, adj2, date2, units2, era2, certainty, lang) -- same as p._complex_date but with extra parameter for certainty: probably, possibly, presumably, etc. local dateStr = p._complex_date(conj, adj1, date1, units1, era1, adj2, date2, units2, era2, lang, 1) certainty = checkAliases(certainty, conj, 'r') local LUT = {probably='Q56644435', presumably='Q18122778', possibly='Q30230067', circa='Q5727902' } if certainty and LUT[certainty] then local state = {} date1, date2, state = processInputParams(conj, adj1, date1, units1, era1, adj2, date2, units2, era2, lang, 1) dateStr = translatePhrase(dateStr, '', certainty, lang, state) dateStr = string.gsub(dateStr, '(%<div style="display: none;"%>date QS:P,[^%<]+)(%</div%>)', '%1,P1480,' .. LUT[certainty] .. '%2' ) end return dateStr end -- ======================================================================= function p.complex_date(frame) -- process inputs local dateStr local args = frame.args if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then args.lang = frame:callParserFunction( "int", "lang" ) -- get user's chosen language end local date1 = args['date1'] or args['2'] or args['date'] or '' local date2 = args['date2'] or args['3'] or '' local conj = args['conj'] or args['1'] or '' local adj1 = args['adj1'] or args['adj'] or '' local adj2 = args['adj2'] or '' local units1 = args['precision1'] or args['precision'] or '' local units2 = args['precision2'] or args['precision'] or '' local era1 = args['era1'] or args['era'] or '' local era2 = args['era2'] or args['era'] or '' local certainty = args['certainty'] local lang = args['lang'] dateStr = p._complex_date_cer(conj, adj1, date1, units1, era1, adj2, date2, units2, era2, certainty, lang) if p.Error~=nil then dateStr = p.Error .. '[[Category:Pages using Complex date template with incorrect parameter]]' end return dateStr end return p ppv1t08ydt8a9x36u2iltdsomq7mcr5 803634 803633 2026-07-30T00:20:17Z SM7 3953 9 revisions imported from [[:en:Module:Complex_date]] 803633 Scribunto text/plain --[[ __ __ _ _ ____ _ _ | \/ | ___ __| |_ _| | ___ _ / ___|___ _ __ ___ _ __ | | _____ __ __| | __ _| |_ ___ | |\/| |/ _ \ / _` | | | | |/ _ (_) | / _ \| '_ ` _ \| '_ \| |/ _ \ \/ / / _` |/ _` | __/ _ \ | | | | (_) | (_| | |_| | | __/_| |__| (_) | | | | | | |_) | | __/> < | (_| | (_| | || __/ |_| |_|\___/ \__,_|\__,_|_|\___(_)\____\___/|_| |_| |_| .__/|_|\___/_/\_\ \__,_|\__,_|\__\___| |_| This module is intended for creation of complex date phrases in variety of languages. Once deployed, please do not modify this code without applying the changes first at Module:Complex date/sandbox and testing at Module:Complex date/sandbox/testcases. Authors and maintainers: * User:Sn1per - first draft of the original version * User:Jarekt - corrections and expansion of the original version ]] -- List of external modules and functions local p = {Error = nil} local i18n = require('Module:i18n/complex date') -- used for translations of date related phrases local ISOdate = require('Module:ISOdate')._ISOdate -- used for parsing dates in YYYY-MM-DD and related formats local Calendar -- loaded lazily -- ================================================== -- === Internal functions =========================== -- ================================================== local function langSwitch(list,lang) local langList = mw.language.getFallbacksFor(lang) table.insert(langList,1,lang) table.insert(langList,math.max(#langList,2),'default') for i,language in ipairs(langList) do if list[language] then return list[language] end end end -- ================================================== local function formatnum1(numStr, lang) -- mostly require('Module:Formatnum').formatNum function used to translate a number to use different numeral characters, -- except that it it does not call that function unless the language is on the list "LList" local LList = {bn=1,bpy=1,kn=1,hi=1,mr=1,new=1,pa=1,gu=1,fa=1,glk=1,mzn=1,ur=1,ar=1,ckb=1,ks=1,lo=1,['or']=1,bo=1,['ml-old']=1,mn=1,te=1,th=1} if LList[lang] then -- call only when the language is on the list numStr = require('Module:Formatnum').formatNum(numStr, lang, 1) end return numStr end -- ================================================== local function getISODate(datestr, datetype, lang, num, case) -- translate dates in the format YYYY, YYYY-MM, and YYYY-MM-DD if not case and i18n.Translations[datetype] then -- look up the grammatical case needed and call ISOdate module local rec = langSwitch(i18n.Translations[datetype], lang) if type(rec)=='table' then case = rec.case[num] end end return ISOdate(datestr, lang, case, '', 1) end -- ======================================================================= local function translatePhrase(date1, date2, operation, lang, state) -- use tables in Module:i18n/complex date to translate a phrase if not i18n.Translations[operation] then p.Error = string.format('<span style="background-color:red;">Error in [[Module:Complex date]]: input parameter "%s" is not recognized.</span>', operation or 'nil') return '' end local dateStr = langSwitch(i18n.Translations[operation], lang) if type(dateStr)=='table' then dateStr = dateStr[1] end if type(dateStr)=='function' then local dateFunc = dateStr local nDates = i18n.Translations[operation]['nDates'] if nDates==2 then -- 2 date phrase dateStr = dateFunc(date1, date2, state) else -- 1 date phrase dateStr = dateFunc(date1, state) end end if type(dateStr)=='string' then -- replace parts of the string '$date1' and '$date2' with date1 and date2 strings dateStr = mw.ustring.gsub(dateStr, '$date1', date1) dateStr = mw.ustring.gsub(dateStr, '$date2', date2) else -- Special case of more complex phrases that can be build out of simple phrases -- If complex case is not translated to "lang" than build it out of simpler ones local x = dateStr dateStr = p._complex_date(x.conj, x.adj1, date1, x.units1, x.era1, x.adj2, date2, x.units2, x.era2, lang, 2) end return dateStr end -- ======================================================================= local function oneDatePhrase(dateStr, adj, era, units, lang, num, case, state) -- translate a single date phrase if num==2 then state.adj, state.era, state.units, state.precision = state.adj2, state.era2, state.units2, state.precision2 end -- dateStr can have many forms: ISO date, year or a number for -- decade, century or millennium if units == '' then -- unit is "year", "month", "day" dateStr = getISODate(dateStr, adj, lang, num, case) else -- units is "decade", "century", "millennium'' dateStr = translatePhrase(dateStr, '', units, lang, state) end -- add adjective ("early", "mid", etc.) or preposition ("before", "after", -- "circa", etc.) to the date if adj ~= '' then dateStr = translatePhrase(dateStr, '', adj, lang, state) else -- only era? dateStr = formatnum1(dateStr, lang) end -- add era if era ~= '' then dateStr = translatePhrase(dateStr, '', era, lang, state) end return dateStr end -- ======================================================================= local function twoDatePhrase(date1, date2, state, lang) -- translate a double date phrase local dateStr, case local era='' if state.era1 == state.era2 then -- if both eras are the same than add it only once era = state.era1 state.era1 = '' state.era2 = '' end case = {nil, nil} if i18n.Translations[state.conj] then local rec = langSwitch(i18n.Translations[state.conj], lang) if type(rec)=='table' then case = rec.case end end date1 = oneDatePhrase(date1, state.adj1, state.era1, state.units1, lang, 1, case[1], state) date2 = oneDatePhrase(date2, state.adj2, state.era2, state.units2, lang, 2, case[2], state) dateStr = translatePhrase(date1, date2, state.conj, lang, state) if era ~= '' then dateStr = translatePhrase(dateStr, '', era, lang, state) end return dateStr end -- ======================================================================= local function otherPhrases(date1, date2, operation, era, lang, state) -- translate specialized phrases local dateStr = '' if operation == 'islamic' then if date2=='' then date2 = mw.getCurrentFrame():callParserFunction('#time', 'xmY', date1) end date1 = getISODate(date1, operation, lang, 1, nil) date2 = getISODate(date2, operation, lang, 2, nil) if era == '' then era = 'ad' end dateStr = translatePhrase(date1, '', era, lang, state) .. ' (' .. translatePhrase(date2, '', 'ah', lang, state) .. ')' era = '' elseif operation == 'julian' then if not date2 and date1 then -- Convert from Julian to Gregorian calendar date if Calendar == nil then Calendar = require("Module:Calendar") -- lazy loding (only if needed) end local JDN = Calendar._date2jdn(date1, 0) if JDN then date2 = date1 -- first date is assumed to be Julian date1 = Calendar._jdn2date(JDN, 1) end end date1 = getISODate(date1, operation, lang, 1, nil) date2 = getISODate(date2, operation, lang, 2, nil) dateStr = translatePhrase(date1, date2, operation, lang, state) dateStr = mw.ustring.gsub(mw.ustring.gsub(dateStr, '%( ', '('), ' %)', ')') -- in case date2 is empty elseif operation == 'turn of the year' or operation == 'turn of the decade' or operation == 'turn of the century' then local dt = 1 if operation == 'turn of the decade' then dt=10 end if not date2 or date2=='' then date2=tostring(tonumber(date1)-dt) end if era~='bp' and era~='bc' then date1, date2 = date2, date1 end if operation == 'turn of the year' then date1 = ISOdate(date1, lang, '', '', 1) date2 = ISOdate(date2, lang, '', '', 1) else date1 = formatnum1(date1, lang) date2 = formatnum1(date2, lang) end dateStr = translatePhrase(date1, date2, operation, lang, state) elseif operation == 'year unknown' then dateStr = translatePhrase('', '', operation, lang, state) .. '<div style="display: none;">Unknown date</div>' elseif operation == 'unknown' then dateStr = tostring(mw.message.new( "exif-unknowndate" ):inLanguage( lang )) .. '<div style="display: none;">Unknown date</div>' end -- add era if era ~= '' then dateStr = translatePhrase(dateStr, '', era, lang, state) end return dateStr end -- ======================================================================= local function checkAliases(str1, str2, sType) -- some inputs have many aliases - reconcile them and ensure string is playing a proper role local out = '' if str1 and str1~='' then local a = i18n.Synonyms[str1] -- look up synonyms of "str1" if a then out = a[1] else p.Error = string.format('<span style="background-color:red;">Error in [[Module:Complex date]]: %s is not recognized.</span>', str1) end elseif str2 and str2~='' then -- if "str1" of type "sType" is empty than maybe ... local a = i18n.Synonyms[str2] -- ..."str2" is of the same type and is not empty if a and a[2]==sType then out = a[1] str2 = '' end end return out, str2 end -- ======================================================================= local function datePrecision(dateStr, units) -- "in this module "Units" is a string like millennium, century, or decade -- "precision" is wikibase compatible date precision number: 6=millennium, 7=century, 8=decade, 9=year, 10=month, 11=day -- based on string or numeric input calculate "Units" and "precision" local precision if type(units)=='number' then precision = units if precision>11 then precision=11 end -- clip the range of precision values if precision==6 then units='millennium' elseif precision==7 then units='century' elseif precision==8 then units='decade' else units = '' end elseif type(units)=='string' then units = string.lower(units) if units=='millennium' then precision=6 elseif units=='century' then precision=7 elseif units=='decade' then precision=8 else precision=9 end end if units=='' or precision==9 then local sLen = mw.ustring.len(dateStr) if sLen<= 4 then precision=9 elseif sLen== 7 then precision=10 elseif sLen>=10 then precision=11 end units='' end if precision==6 and dateStr.match( dateStr, '%d000' )~=nil then dateStr = tostring(math.floor(tonumber(dateStr)/1000) +1) elseif precision==7 and mw.ustring.match( dateStr, '%d%d00' )~=nil then dateStr = tostring(math.floor(tonumber(dateStr)/100) +1) end return dateStr, units, precision end -- ======================================================================= local function isodate2timestamp(dateStr, precision, era) -- convert date string to timestamps used by Quick Statements local tStamp = nil if era == 'ah' or precision<6 then return nil elseif era ~= '' then local eraLUT = {ad='+', bc='-', bp='-' } era = eraLUT[era] else era='+' end -- convert isodate to timestamp used by quick statements if precision>=9 then if string.match(dateStr,"^%d%d%d%d$") then -- if YYYY format tStamp = era .. dateStr .. '-00-00T00:00:00Z/9' elseif string.match(dateStr,"^%d%d%d%d%-%d%d$") then -- if YYYY-MM format tStamp = era .. dateStr .. '-00T00:00:00Z/10' elseif string.match(dateStr,"^%d%d%d%d%-%d%d%-%d%d$") then -- if YYYY-MM-DD format tStamp = era .. dateStr .. 'T00:00:00Z/11' end elseif precision==8 then -- decade tStamp = era .. dateStr .. '-00-00T00:00:00Z/8' elseif precision==7 then -- century local d = tostring(tonumber(dateStr)-1) tStamp = era .. d .. '50-00-00T00:00:00Z/7' elseif precision==6 then local d = tostring(tonumber(dateStr)-1) tStamp = era .. d .. '500-00-00T00:00:00Z/6' end return tStamp end -- ======================================================================= local function oneDateQScode(dateStr, adj, era, precision) -- create QuickStatements string for "one date" dates local outputStr = '' local d = isodate2timestamp(dateStr, precision, era) if not d then return '' end local rLUT = { early='Q40719727' , mid='Q40719748', late='Q40719766', ['1quarter']='Q40690303' , ['2quarter']='Q40719649' , ['3quarter']='Q40719662', ['4quarter']='Q40719674', spring='Q40720559' , summer='Q40720564' , autumn='Q40720568' , winter='Q40720553', firsthalf='Q40719687', secondhalf='Q40719707' } local qLUT = {['from']='P580', ['until']='P582', ['after']='P1319', ['before']='P1326', ['by']='P1326'} local refine = rLUT[adj] local qualitier = qLUT[adj] if adj=='' then outputStr = d elseif adj=='circa' then outputStr = d..",P1480,Q5727902" elseif refine then outputStr = d..",P4241,"..refine elseif precision>7 and qualitier then local century = string.gsub(d, 'Z%/%d+', 'Z/7') outputStr = century ..",".. qualitier ..","..d end return outputStr end -- ======================================================================= local function twoDateQScode(date1, date2, state) -- create QuickStatements string for "two date" dates if state.adj1~='' or state.adj2~='' or state.era1~=state.era2 then return '' -- QuickStatements string are not generated for two date phrases with adjectives end local outputStr = '' local d1 = isodate2timestamp(date1, state.precision1, state.era1) local d2 = isodate2timestamp(date2, state.precision2, state.era2) if (not d1) or (not d2) then return '' end -- find date with lower precision in common to both dates local cd local year1 = tonumber(string.sub(d1,2,5)) local year2 = tonumber(string.sub(d2,2,5)) local k = 0 for i = 1,10,1 do if string.sub(d1,1,i)==string.sub(d2,1,i) then k = i -- find last matching letter end end if k>=9 then -- same month, since "+YYYY-MM-" is in common cd = isodate2timestamp(string.sub(d1,2,8), 10, state.era1) elseif k>=6 and k<9 then -- same year, since "+YYYY-" is in common cd = isodate2timestamp(tostring(year1), 9, state.era1) elseif k==4 then -- same decade(k=4, precision=8), since "+YYY" is in common cd = isodate2timestamp(tostring(year1), 8, state.era1) elseif k==3 then -- same century(k=3, precision=7) since "+YY" is in common local d = tostring(math.floor(year1/100) +1) -- convert 1999 -> 20 cd = isodate2timestamp( d, 7, state.era1) elseif k==2 then -- same millennium (k=2, precision=6), since "+Y" is in common local d = tostring(math.floor(year1/1000) +1) -- convert 1999 -> 2 cd = isodate2timestamp( d, 6, state.era1) end if not cd then return '' end --if not cd then -- return ' <br/>error: ' .. d1.." / " .. d2.." / ".. (cd or '') .." / ".. string.sub(d1,2,5).." / " .. string.sub(d2,2,5).." / " .. tostring(k) --end -- if (state.conj=='from-until') or (state.conj=='and' and year1==year2-1) then outputStr = cd ..",P580,".. d1 ..",P582,".. d2 elseif (state.conj=='between') or (state.conj=='or' and year1==year2-1) then outputStr = cd ..",P1319,".. d1 ..",P1326,".. d2 elseif state.conj=='circa2' then outputStr = cd ..",P1319,".. d1 ..",P1326,".. d2 ..",P1480,Q5727902" end return outputStr end -- ======================================================================= local function processInputParams(conj, adj1, date1, units1, era1, adj2, date2, units2, era2, lang, passNr) -- process inputs and save date in state array local state = {} state.conj = string.lower(conj or '') state.adj1 = string.lower(adj1 or '') state.adj2 = string.lower(adj2 or '') state.era1 = string.lower(era1 or '') state.era2 = string.lower(era2 or '') state.units1 = string.lower(units1 or '') state.units2 = string.lower(units2 or '') -- if date 1 is missing but date 2 is provided than swap them if date1 == '' and date2 ~= '' then date1 = date2 date2 = '' state = {adj1 = state.adj2, era1 = state.era2, units1 = state.units2, adj2 = '', era2 = '', units2 = '', conj=state.conj, num=1} end if date2 ~= '' then state.nDates = 2 elseif date1 ~= '' then state.nDates = 1 else state.nDates = 0 end -- reconcile alternative names for text inputs local conj = checkAliases(state.conj ,'' ,'j') state.adj1 ,conj = checkAliases(state.adj1 ,conj,'a') state.units1,conj = checkAliases(state.units1,conj,'p') state.era1 ,conj = checkAliases(state.era1 ,conj,'e') state.special,conj = checkAliases('',conj,'c') state.adj2 = checkAliases(state.adj2 ,'','a') state.units2 = checkAliases(state.units2,'','p') state.era2 = checkAliases(state.era2 ,'','e') state.conj = conj state.lang = lang if p.Error~=nil then return nil end -- calculate date precision value date1, state.units1, state.precision1 = datePrecision(date1, state.units1) date2, state.units2, state.precision2 = datePrecision(date2, state.units2) -- Handle special cases -- Some complex phrases can be created out of simpler ones. Therefore on pass # 1 we try to create -- the phrase using complex phrase and if that is not found than on the second pass we try to build -- the phrase out of the simpler ones if passNr==1 then if state.adj1=='circa' and state.nDates == 2 then state.conj = 'circa2' state.adj1 = '' state.adj2 = '' end if state.nDates == 2 and state.adj1=='late' and state.adj2=='early' and state.conj=='and' and state.units1==state.units2 and state.era1==state.era2 then if state.units1=='century' then state.conj='turn of the century' elseif state.units1=='decade' then state.conj='turn of the decade' elseif state.units1=='' then state.conj='turn of the year' end state.adj1 = '' state.adj2 = '' state.units1 = '' state.units2 = '' end end state.adj, state.era, state.units, state.precision = state.adj1, state.era1, state.units1, state.precision1 return date1, date2, state end -- ================================================== -- === External functions =========================== -- ================================================== function p.Era(frame) -- process inputs local dateStr local args = frame.args if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then args.lang = frame:callParserFunction( "int", "lang" ) -- get user's chosen language end local lang = args['lang'] local dateStr = args['date'] or '' local eraType = string.lower(args['era'] or '') dateStr = ISOdate(dateStr, lang, '', '', 1) if eraType then eraType = checkAliases(eraType ,'','e') dateStr = translatePhrase(dateStr, '', eraType, lang, {}) end return dateStr end -- ======================================================================= function p._complex_date(conj, adj1, date1, units1, era1, adj2, date2, units2, era2, lang, passNr) local Output='' local state -- process inputs and save date in state array date1, date2, state = processInputParams(conj, adj1, date1, units1, era1, adj2, date2, units2, era2, lang, passNr) if p.Error~=nil then return nil end local errorStr = string.format( '\n*conj=%s, adj1=%s, era1=%s, unit1=%s, prec1=%i, adj2=%s, era2=%s, unit2=%s, prec2=%i, special=%s', state.conj, state.adj1, state.era1, state.units1, state.precision1, state.adj2, state.era2, state.units2, state.precision2, state.special) -- call specialized functions local QScode = '' if state.special~='' then Output = otherPhrases(date1, date2, state.special, state.era1, lang, state) elseif state.conj~='' then QScode = twoDateQScode(date1, date2, state) Output = twoDatePhrase(date1, date2, state, lang) elseif state.adj1~='' or state.era1~='' or state.units1~='' then Output = oneDatePhrase(date1, state.adj1, state.era1, state.units1, lang, 1, nil, state) QScode = oneDateQScode(date1, state.adj1, state.era1, state.precision1) elseif date1~='' then Output = ISOdate(date1, lang, '', 'dtstart', '100-999') end if p.Error~=nil then return errorStr end -- if there is any wikicode in the string than execute it if mw.ustring.find(Output, '{') then Output = mw.getCurrentFrame():preprocess(Output) end if QScode and #QScode>0 then QScode = ' <div style="display: none;">date QS:P,' .. QScode .. '</div>' end return Output .. QScode end -- ======================================================================= function p._complex_date_cer(conj, adj1, date1, units1, era1, adj2, date2, units2, era2, certainty, lang) -- same as p._complex_date but with extra parameter for certainty: probably, possibly, presumably, etc. local dateStr = p._complex_date(conj, adj1, date1, units1, era1, adj2, date2, units2, era2, lang, 1) certainty = checkAliases(certainty, conj, 'r') local LUT = {probably='Q56644435', presumably='Q18122778', possibly='Q30230067', circa='Q5727902' } if certainty and LUT[certainty] then local state = {} date1, date2, state = processInputParams(conj, adj1, date1, units1, era1, adj2, date2, units2, era2, lang, 1) dateStr = translatePhrase(dateStr, '', certainty, lang, state) dateStr = string.gsub(dateStr, '(%<div style="display: none;"%>date QS:P,[^%<]+)(%</div%>)', '%1,P1480,' .. LUT[certainty] .. '%2' ) end return dateStr end -- ======================================================================= function p.complex_date(frame) -- process inputs local dateStr local args = frame.args if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then args.lang = frame:callParserFunction( "int", "lang" ) -- get user's chosen language end local date1 = args['date1'] or args['2'] or args['date'] or '' local date2 = args['date2'] or args['3'] or '' local conj = args['conj'] or args['1'] or '' local adj1 = args['adj1'] or args['adj'] or '' local adj2 = args['adj2'] or '' local units1 = args['precision1'] or args['precision'] or '' local units2 = args['precision2'] or args['precision'] or '' local era1 = args['era1'] or args['era'] or '' local era2 = args['era2'] or args['era'] or '' local certainty = args['certainty'] local lang = args['lang'] dateStr = p._complex_date_cer(conj, adj1, date1, units1, era1, adj2, date2, units2, era2, certainty, lang) if p.Error~=nil then dateStr = p.Error .. '[[Category:Pages using Complex date template with incorrect parameter]]' end return dateStr end return p ppv1t08ydt8a9x36u2iltdsomq7mcr5 Module:I18n/complex date 828 65953 803606 652102 2019-02-24T14:21:17Z en>RexxS 0 re-sync from Commons 803606 Scribunto text/plain --[[ __ __ _ _ ___ _ ___ __ _ _ _ | \/ | ___ __| |_ _| | ___ _|_ _/ |( _ ) _ __ / /__ ___ _ __ ___ _ __ | | _____ __ __| | __ _| |_ ___ | |\/| |/ _ \ / _` | | | | |/ _ (_)| || |/ _ \| '_ \ / / __/ _ \| '_ ` _ \| '_ \| |/ _ \ \/ / / _` |/ _` | __/ _ \ | | | | (_) | (_| | |_| | | __/_ | || | (_) | | | |/ / (_| (_) | | | | | | |_) | | __/> < | (_| | (_| | || __/ |_| |_|\___/ \__,_|\__,_|_|\___(_)___|_|\___/|_| |_/_/ \___\___/|_| |_| |_| .__/|_|\___/_/\_\ \__,_|\__,_|\__\___| |_| This code was copied from Commons please request changes there. Maintainers: *Jarekt ]] local Ordinal = require('Module:Ordinal')._Ordinal local Roman = require('Module:Roman')._Numeral local linguistic = require('Module:Linguistic') -- ========================================================================== -- === Local Helper functions ============================================== -- ========================================================================== -- form of French "of" based on "precision", which is wikibase compatible date precision format: -- 6=millennium, 7=century, 8=decade, 9=year, 10=month, 11=day -- 1 2 3 4 5 mill cent deca year month day local frOf = {'du','du','du','du','du', 'du', 'du', 'de', 'de', 'de', 'du'} local function decYear(state,lang,case) --Declension of word "year" in several languages if state.precision == 9 then yearCase = { fi={xxx='vuosien', gen='vuoden', ill='vuoteen', ptv='vuotta', ela='vuodesta', ine='vuonna'}, sl={insP='letoma', ins='letom', gen='leta', loc='letu'}, hsb={insP='lětami', ins='lětom', gen='lěta', loc='lěće'} } return yearCase[lang][case] .. ' ' else return '' end end local function decCentury(lang,case) --Declension of word "century" in several languages centuryCase = { pl={nom='wiek', gen='wieku', ins='wiekiem', loc='wieku', default = 'wiek'}, hsb={nom='lětstotk', gen='lětstotka', ins='lětstotkom', loc='lětstotku', default = 'lětstotk'} } return centuryCase[lang][case] or centuryCase[lang]['default'] end local function decMillennium(lang,case) --Declension of word "century" in several languages millenniumCase = { pl={nom='tysiąclecie', gen='tysiąclecia', ins='tysiącleciem', loc='tysiącleciu', default = 'tysiąclecie'}, hsb={nom='lěttysac', gen='lěttysaca', ins='lěttysacom', loc='lěttysacu', default = 'lěttysac'} } return millenniumCase[lang][case] or millenniumCase[lang]['default'] end -- ========================================================================== -- === Output data structure =============================================== -- ========================================================================== local p = {} --[[ Multiple formats are possible: 1) string with key words "$date1" and "$date2" which will be replaced later by the date strings 2) function which can be used to create output string * Function INPUTS: - date1 and date2 string with date strings. "$date1" and "$date2" can also be used which will be replaced latter - state is a table with full description of the phrase we are trying to construct. It includes state.precision1 (and 2) which is wikibase compatible date precision format: 6=millennium, 7=century, 8=decade, 9=year, 10=month, 11=day 3) table with following fields: * table[1] is either string or a function * table.case is a 2 element table with Grammatical cases used by given language to alter month name or other adjective related to date # 1 and 2 ]] p.Translations = { -- ========================================== -- === Two date phrases ===================== -- ========================================== ['from-until'] = { nDates = 2, ar='من $date1 إلى $date2', ['be-tarask']={'з $date1 да $date2', case={'gen','gen'}}, bn='$date1 থেকে $date2 পর্যন্ত', ca='$date1 fins $date2', cs='$date1 až $date2', da='fra $date1 til $date2', de='$date1 bis $date2', el='από $date1 έως $date2', en='from $date1 until $date2', ['en-gb']='from $date1 until $date2', ['en-ca']='from $date1 until $date2', eo='de $date1 al $date2', es='de $date1 a $date2', et='$date1 kuni $date2', fa='از $date1 تا $date2', fi='$date1–$date2', -- välisenä aikana fr=function(date1, date2, state) -- could actually be better should be "d'octobre à mars 2013' not "de octore 2013 à mars 2013" if state.precision > 10 then -- precision > month return 'du $date1 au $date2' else -- "DE septebmbre" / "D'octobre" return linguistic.of(date1, 'fr') .. " à $date2" end end, gl='de $date1 a $date2', --Template:From until used 'desde $date1 ata $date2' he='מ- $date1 עד $date2', hr='od $date1 do $date2', hsb={'wot $date1 do $date2', case={'gen','gen'}}, hu='$date1 – $date2', it='dal $date1 al $date2', ja='$date1から$date2まで', mk='од $date1 до $date2', ml='$date1 മുതൽ $date2 വരെ', nb='fra $date1 til $date2', nds='$date1 bet $date2', nn='fra $date1 til $date2', nl='$date1 tot $date2', no='fra $date1 til $date2', pl={'od $date1 do $date2', case={'gen','gen'}}, ro='din $date1 până în $date2', pt='de $date1 a $date2', qu="$date1 p'unchawmanta $date2 p\'unchawkama", ru={'с $date1 по $date2', case={'gen','gen'}}, sk={'od $date1 do $date2', case={'gen','gen'}}, sl={'od $date1 do $date2', case={'gen','gen'}}, sv='från $date1 till $date2', uk={'з $date1 до $date2', case={'gen','gen'}}, ur='از $date1 تا $date2', vi='từ $date1 đến $date2', ['zh-hans']='从$date1到$date2', ['zh-hant']='從$date1到$date2', default='<span style="color:red">$date1 - $date2</span>', }, ['between']={ nDates = 2, ar='بين $date1 و $date2', ['be-tarask']={'між $date1 і $date2', case={'ins','ins'}}, bn='$date1 এবং $date2-এর মাঝামাঝি', ca='entre $date1 i $date2', cs={'mezi $date1 a $date2', case={'ins','ins'}}, da='mellem $date1 og $date2', de='zwischen $date1 und $date2', el='μεταξύ $date1 και $date2', en='between $date1 and $date2', ['en-gb']='between $date1 and $date2', ['en-ca']='between $date1 and $date2', eo='inter $date1 kaj $date2', es='entre $date1 y $date2', et='$date1 ja $date2 vahel', fa='بین $date1 و $date2', fi={function(date1, date2, state) return decYear(state,'fi','xxx') .. '$date1 ja $date2 välisenä aikana' end, case={'gen','gen'}}, fr='entre $date1 et $date2', gl='entre $date1 e $date2', he='בין $date1 ו- $date2', hr='između $date1 i $date2', hsb={'mjez $date1 a $date2', case={'ins','ins'}}, hu='$date1 és $date2 között', hi='$date1 और $date2 के बीच', it='tra il $date1 e il $date2', ja='$date1と$date2の間', mk='помеѓу $date1 и $date2', ml='$date1, $date2 എന്നിവയ്ക്കിടയ്ക്ക്', nb='mellom $date1 og $date2', nds='twischen $date1 un $date2', nn='mellom $date1 og $date2', nl='tussen $date1 en $date2', no='mellom $date1 og $date2', pl={'między $date1 a $date2', case={'ins','ins'}}, ro='între $date1 și $date2', pt='entre $date1 e $date2', qu="$date1 p'unchawpa $date2 p'unchawpapas chawpin", ru={'между $date1 и $date2', case={'ins','ins'}}, sk={'medzi $date1 a $date2', case={'ins','ins'}}, sl={function(date1, date2, state) return 'med ' .. decYear(state,'sl','insP') .. '$date1 in $date2' end, case={'ins','ins'}}, sv='mellan $date1 och $date2', uk={'між $date1 та $date2', case={'ins','ins'}}, ur='بین $date1 و $date2', vec='tra el $date1 e el $date2', vi='giữa $date1 và $date2', ['zh-hans']='在$date1到$date2之间', ['zh-hant']='在$date1到$date2之間', default='<span style="color:red">$date1 - $date2</span>', }, ['or'] = { nDates = 2, ar='$date1 أو $date2', ['be-tarask']='$date1 ці $date2', bn='$date1 অথবা $date2', ca='$date1 o $date2', cs='$date1 nebo $date2', da='$date1 eller $date2', de='$date1 oder $date2', el='$date1 ή $date2', en='$date1 or $date2', ['en-gb']='$date1 or $date2', ['en-ca']='$date1 or $date2', eo='$date1 aŭ $date2', es='$date1 o $date2', et='$date1 või $date2', fa='$date1 یا $date2', fi='$date1 tai $date2', fr='$date1 ou $date2', gl='$date1 ou $date2', he='$date1 או $date2', hr='$date1 ili $date2', hsb='$date1 abo $date2', hu='$date1 vagy $date2', it='$date1 o $date2', ja='$date1または$date2', mk='$date1 или $date2', ml='$date1 അഥവാ $date2', nb='$date1 eller $date2', nn='$date1 eller $date2', nds='$date1 oder $date2', nl='$date1 of $date2', no='$date1 eller $date2', pl='$date1 lub $date2', pt='$date1 ou $date2', qu='$date1 icha $date2', ro='$date1 sau $date2', ru='$date1 или $date2', sk='$date1 alebo $date2', sl='$date1 ali $date2', sv='$date1 eller $date2', uk='$date1 або $date2', ur='$date1 یا $date2', th='$date1 หรือ $date2', vi='$date1 hoặc $date2', ['zh-hans']='$date1或$date2', ['zh-hant']='$date1或$date2', default=function(date1, date2, state) return linguistic.conj({date1, date2}, state.lang, 'or') end, }, ['and'] = { nDates = 2, ar='$date1 و $date2', ['be-tarask']='$date1 і $date2', bn='$date1 এবং $date2', ca='$date1 i $date2', cs='$date1 a $date2', da='$date1 og $date2', de='$date1 und $date2', el='$date1 και $date2', en='$date1 and $date2', ['en-gb']='$date1 and $date2', ['en-ca']='$date1 and $date2', eo='$date1 kaj $date2', es='$date1 y $date2', et='$date1 ja $date2', fa='$date1 و $date2', fi='$date1 ja $date2', fr='$date1 et $date2', gl='$date1 e $date2', he='$date1 ו- $date2', hu='$date1 és $date2', hsb='$date1 a $date2', it='$date1 e $date2', ja='$date1および$date2', mk='$date1 и $date2', ml='$date1 ഒപ്പം $date2', nb='$date1 og $date2', nn='$date1 og $date2', nds='$date1 un $date2', nl='$date1 en $date2', no='$date1 og $date2', pl='$date1 i $date2', pt='$date1 e $date2', qu='$date1wan $date2pas', ro='$date1 și $date2', ru='$date1 и $date2', sk='$date1 a $date2', sl='$date1 in $date2', sv='$date1 och $date2', uk='$date1 та $date2', ur='$date1 و $date2', vi='$date1 và $date2', ['zh-hans']='$date1和$date2', ['zh-hant']='$date1和$date2', default=function(date1, date2, state) return linguistic.conj({date1, date2}, state.lang, 'and') end, }, -- ========================================== -- === Prepositions ========================= -- ========================================== ['by'] = { nDates = 1, ar='حوالي $date1', ['be-tarask']={'да $date1', case={'gen',''}}, bn='$date1 কর্তৃক', ca='vers $date1', cs={'kolem $date1', case={'gen',''}}, da='mod $date1', de='spätestens $date1', el='έως $date1', en='by $date1', ['en-gb']='by $date1', ['en-ca']='by $date1', eo='ne post $date1', es='para $date1', et='hiljemalt $date1', fa='توسط $date1', fi={function(date1, state) return decYear(state,'fi','ill') .. '$date1 mennessä' end, case={'ill',''}}, fr='$date1 (au plus tard)', gl='cara a $date1', he='עד $date1', hr='do $date1', hsb={'do $date1', case={'gen',''}}, it='entro il $date1', ja='$date1まで', mk='кон $date1', ml='$date1-ൽ', nb='innen $date1', nn='innan $date1', nds='$date1 oder vördem al', nl='tegen $date1', no='innen $date1', pl={'przed $date1', case={'ins',''}}, pt='por $date1', qu="$date1 p'unchawkama", ro='de $date1', ru={'к $date1', case={'dat',''}}, sk={'okolo $date1', case={'gen',''}}, sl={function(date1, state) return 'najpozneje ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='senast $date1', uk={'до $date1', case={'dat',''}}, ur='توسط $date1', vi='vào $date1', -- need to insert "đã" somewhere later in the sentence --> ['zh-hans']='$date1前后', ['zh-hant']='$date1前后', default='<span style="color:red">by $date1</span>', }, ['before'] = { nDates = 1, ar='قبل $date1', ['be-tarask']={'перад $date1', case={'ins',''}}, bn='$date1-এর আগে', bg='Преди $date1', ca='abans $date1', cs={'před $date1', case={'ins',''}}, da='før $date1', de='vor $date1', el='πριν από $date1', en='before $date1', ['en-gb']='before $date1', ['en-ca']='before $date1', eo='antaŭ $date1', es='antes de $date1', et='enne $date1', fa='پیش از $date1', fi={function(date1, state) return 'ennen ' .. decYear(state,'fi','ptv') .. '$date1' end, case={'ptv',''}}, fr='avant $date1', gl='antes do $date1', he='לפני $date1', hr='prije $date1', hsb={'do $date1', case={'gen',''}}, hu='$date1 előtt', it='prima del $date1', ja='$date1より前', mk='пред $date1', ml='$date1-നു മുമ്പ്', nb='før $date1', nn='før $date1', nds='vör $date1', nl='voor $date1', no='før $date1', pl={'przed $date1', case={'ins',''}}, pt='antes de $date1', qu="$date1 p'unchaw ñawpan", ro='înainte de $date1', ru={'ранее $date1', case={'gen',''}}, sk={'pred $date1', case={'ins',''}}, sl={function(date1, state) return 'pred ' .. decYear(state,'sl','ins') .. ' $date1' end, case={'ins',''}}, sv='före $date1', uk={'до $date1', case={'gen',''}}, ur='قبل $date1', vi='trước $date1', ['zh-hans']='在$date1之前', ['zh-hant']='在$date1之前', default='<span style="color:red">before $date1</span>', }, ['from'] = { nDates = 1, ar='من $date1', ['be-tarask']={'з $date1', case={'gen',''}}, bn='$date1 থেকে', cs={'od $date1', case={'gen',''}}, de='Seit $date1', el='από $date1', en='from $date1', ['en-gb']='from $date1', ['en-ca']='from $date1', eo='ekde $date1', es='desde $date1', et='alates $date1', fa='از $date1', fi={function(date1, state) return decYear(state,'fi','ela') .. '$date1 alkaen' end, case={'ela',''}}, fr= function(date1, state) return 'à partir '.. frOf[state.precision] ..' $date1' end, gl='desde o $date1', he='מ- $date1', hr='od $date1', hsb={'wot $date1', case={'gen',''}}, it='dal $date1', ja='$date1から', nds='vonaf $date1', mk='од $date1', nb='fra $date1', nn='frå $date1', ml='$date1 മുതൽ', nl='vanaf $date1', no='fra $date1', pl={'od $date1', case={'gen',''}}, qu="$date1 p'unchawmanta", ro='din $date1', ru={'С $date1', case={'gen',''}}, sk={'od $date1', case={'gen',''}}, sl={function(date1, state) return 'od ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='från $date1', uk={'з $date1', case={'gen',''}}, ur='از $date1', vi='từ $date1', ['zh-hans']='自$date1', ['zh-hant']='自$date1', default='<span style="color:red">from $date1</span>', }, ['until'] = { nDates = 1, ['be-tarask']={'усутыч да $date1', case={'gen',''}}, bn='$date1 পর্যন্ত', cs={'do $date1', case={'gen', ''}}, de='bis $date1', en='until $date1', ['en-gb']='until $date1', ['en-ca']='until $date1', eo='ĝis $date1', es='hasta $date1', fr=function(date1, state) if state.precision > 10 then -- precision > month return "jusqu'au $date1" else return "jusqu'à $date1" end end, hr='do $date1', hsb={'do $date1', case={'gen',''}}, it='al $date1', nb='til $date1', nl='tot $date1', nn='til $date1', ml='$date1 വരെ', pl={'do $date1', case={'gen',''}}, ru={'вплоть до $date1', case={'gen',''}}, sk={'do $date1', case={'gen', ''}}, uk={'аж до $date1', case={'gen',''}}, ur='تک $date1', ['zh-hans']='直到$date1', ['zh-hant']='直到$date1', default='<span style="color:red">until $date1</span>', }, ['after'] = { nDates = 1, ar='بعد $date1', ['be-tarask']={'пасьля $date1', case={'gen',''}}, bn='$date1-এর পরে', ca='després de $date1', cs={'po $date1', case={'loc',''}}, de='nach $date1', da='efter $date1', el='μετά από $date1', en='after $date1', ['en-gb']='after $date1', ['en-ca']='after $date1', eo='post $date1', es='después de $date1', et='pärast $date1', fa='پس از $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 jälkeen' end, case={'gen',''}}, fr='après $date1', gl='despois do $date1', he='לאחר $date1', hr='nakon $date1', hu='$date1 után', hsb={'po $date1', case={'loc',''}}, it='dopo il $date1', ja='$date1より後', mk='по $date1', ml='$date1-നു ശേഷം', nb='etter $date1', nn='etter $date1', nds='na $date1', nl='na $date1', no='etter $date1', pl={'po $date1', case={'loc',''}}, pt='após $date1', qu="$date1 p'unchaw qhipan", ro='după $date1', ru={'после $date1', case={'gen',''}}, sk={'po $date1', case={'loc',''}}, sl={function(date1, state) return 'po ' .. decYear(state,'sl','loc') .. ' $date1' end, case={'loc',''}}, sv='efter $date1', uk={'після $date1', case={'gen',''}}, ur='بعد $date1', vi='sau $date1', ['zh-hans']='在$date1之后', ['zh-hant']='在$date1之後', default='<span style="color:red">after $date1</span>', }, -- ========================================== -- === Adjectives =========================== -- ========================================== ['early'] = { nDates = 1, ar='بداية $date1', ['be-tarask']={'пачатак $date1', case={'gen',''}}, bn='$date1-এর শুরুতে', ca='al principi de $date1', cs={'začátek $date1', case={'gen',''}}, da='tidlig $date1', de='Anfang $date1', el='αρχές του $date1', en='early $date1', ['en-gb']='early $date1', ['en-ca']='early $date1', eo='komence de $date1', es='a principios de $date1', et='$date1 algus', fa='اوایل $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 alku' end, case={'gen',''}}, fr='début $date1', gl='a principios de $date1', he='תחילת $date1', hr='početak $date1', hsb={'spočatk $date1', case={'gen',''}}, hu='$date1 elején', it='inizio del $date1', ja='$date1初頭', mk='почеток на $date1', ml='$date1 ആദ്യപാദം', nb='tidlig $date1', nn='tidleg $date1', nds='Anfang $date1', nl='begin $date1', no='tidlig $date1', pl={'początek $date1', case={'gen',''}}, pt='a princípios de $date1', qu="$date1 ñawpaq p'unchawkunapi", ro='începând cu $date1', ru={'начало $date1', case={'gen',''}}, sk={'začiatok $date1', case={'gen',''}}, sl=function(date1, state)-- could actually be better should be "d'octobre à mars 2013' not "de octore 2013 à mars 2013" if state.precision == 9 then -- precision > month return 'zgodnje leto $date1' else -- "DE septebmbre" / "D'octobre" return 'zgodnji $date1' end end, sv='början av $date1', uk={'початок $date1', case={'gen',''}}, ur='ابتدا $date1', vi='đầu năm $date1', ['zh-hans']='$date1早期', ['zh-hant']='$date1早期', default='<span style="color:red">early $date1</span>', }, ['mid'] = { nDates = 1, ar='منتصف $date1', ['be-tarask']={'сярэдзіна $date1', case={'gen',''}}, bn='$date1-এর মাঝামাঝি', ca='a mitjan $date1', cs={'polovina $date1', case={'gen',''}}, da='midten af $date1', de='Mitte $date1', el='μέσα του $date1', en='mid $date1', ['en-gb']='mid $date1', ['en-ca']='mid $date1', eo='meze de $date1', es='a mediados de $date1', et='$date1 keskel', fa='اواسط $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 puoliväli' end, case={'gen',''}}, fr='mi $date1', gl='a mediados de $date1', he='אמצע $date1', hr='sredina $date1', hsb={'srjedź $date1', case={'gen',''}}, hu='$date1 közepén', it='metà del $date1', ja='$date1半ば', mk='средина на $date1', ml='$date1 മദ്ധ്യം', nb='midten av $date1', nn='midten av $date1', nds='Midd $date1', nl='midden $date1', no='midten av $date1', pl={'środek $date1', case={'gen',''}}, pt='meados de $date1', qu="$date1 chawpin p'unchawkunapi", ro='mijlocul lui $date1', ru={'середина $date1', case={'gen',''}}, sk={'polovica $date1', case={'gen',''}}, sl={function(date1, state) return 'sredina ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='mitten av $date1', uk={'середина $date1', case={'gen',''}}, ur='وسط $date1', vi='giữa năm $date1', ['zh-hans']='$date1中期', ['zh-hant']='$date1中期', default='<span style="color:red">mid $date1</span>', }, ['late'] = { nDates = 1, ar='نهاية $date1', bn='$date1-এর শেষে', ['be-tarask']={'канец $date1', case={'gen',''}}, ca='al final de $date1', cs='konec $date1', da='sen $date1', de='Ende $date1', el='τέλη του $date1', en='late $date1', ['en-gb']='late $date1', ['en-ca']='late $date1', eo='fine de $date1', es='a fines de $date1', et='$date1 lõpp', fa='اواخر $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 loppu' end, case={'gen',''}}, fr='fin $date1', gl='a finais de $date1', he='סוף $date1', hr='kraj $date1', hsb={'kónc $date1', case={'gen',''}}, hu='$date1 végén', it='fine del $date1', ja='$date1末', mk='крајот на $date1', ml='$date1 അന്ത്യപാദം', nb='sent $date1', no='seint $date1', nds='Enn $date1', nl='eind $date1', no='sent $date1', pl={'koniec $date1', case={'gen',''}}, pt='ao final de $date1', qu="$date1 qhipaq p'unchawkunapi", ro='sfârșitul lui $date1', ru={'конец $date1', case={'gen',''}}, sk={'koniec $date1', case={'gen',''}}, sl={function(date1, state) return 'pozno ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='slutet av $date1', uk={'кінець $date1', case={'gen',''}}, ur='اخیر $date1', vi='cuối năm $date1', ['zh-hans']='$date1晚期', ['zh-hant']='$date1晚期', default='<span style="color:red">late $date1</span>', }, ['circa'] = { nDates = 1, ar='حوالي $date1', ['be-tarask']={'каля $date1', case={'gen',''}}, bn='প্রায় $date1', ca='cap a $date1', cs='cca $date1', da='ca. $date1', de='etwa $date1', el='περ. $date1', en='circa $date1', ['en-gb']='circa $date1', ['en-ca']='circa $date1', eo='ĉirkaŭ $date1', es='hacia $date1', -- suggestion use "hacia"/"hacia el" or leave the latin circa or ca. et='ca. $date1', fa='حوالی $date1', -- template:Circa used 'حدود $date1' fi={function(date1, state) return 'suunnilleen ' .. decYear(state,'fi','ine') .. '$date1' end, case={'ine',''}}, -- template:Circa used 'noin $date1' fr=function(date1, state) if state.precision >= 11 then -- precision >= day return 'vers le $date1' else return 'vers $date1' end end, gl='entre $date1', -- template:Circa used 'cara a $date1' he='בסביבות $date1', hr='oko $date1', hsb={'wokoło $date1', case={'gen',''}}, hu='$date1 körül', it='$date1 circa', ja='$date1頃', ko='$date1경', mk='~ $date1', -- template:Circa used 'o. $date1' ml= 'ഉദ്ദേശം $date1', nb='ca. $date1', nn='ca. $date1', nds='üm un bi $date1', nl='ca. $date1', no='ca. $date1', pl={'około $date1', case={'gen',''}}, pt='cerca de $date1', qu='$date1chá', ro='circa $date1', ru={'около $date1', case={'gen',''}}, sk='cca $date1', sl={function(date1, state) return 'ok. ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='c:a $date1', -- template:Circa used 'ca $date1' uk={'близько $date1', case={'gen', ''}}, ur='قریباً $date1', vi='vào khoảng $date1', ['zh-hans']='约$date1', ['zh-hant']='約$date1', default='<span style="color:red">circa $date1</span>', }, ['uncertain'] = { nDates = 1, ['zh-hans']='$date1(?)', ['zh-hant']='$date1(?)', default='$date1 (?)', }, probably = { -- see Q56644435 nDates = 1, ast = 'probablemente $date1', bn = 'সম্ভবত $date1', ca = 'probablement $date1', cs = 'pravděpodobně $date1', de = 'wahrscheinlich $date1', el = 'πιθανόν $date1', en = 'probably $date1', ['en-gb']='probably $date1', ['en-ca']='probably $date1', eo = 'probable $date1', es = 'probable $date1', fa = 'احتمالاً $date1', fr = 'probablement $date1', he = 'כפי הנראה $date1', hu = 'feltehetőleg $date1', it = 'probabile $date1', mk = 'веројатно $date1', ml = 'ഒരുപക്ഷേ $date1', nl = 'waarschijnlijk $date1', pl = 'prawdopodobnie $date1', pt = 'provavelmente $date1', ru = 'вероятно $date1', sv = 'antagligen $date1', uk = 'ймовірно $date1', default='<span style="color:red">probably $date1</span>', }, possibly = { -- see Q30230067 nDates = 1, ast = 'posiblemente $date1', bn = 'যথাসম্ভব $date1', cs = 'možná $date1', de = 'möglicherweise $date1', en = 'possibly $date1', ['en-gb']='possibly $date1', ['en-ca']='possibly $date1', eo = 'eble $date1', el = 'πιθανόν $date1', fa = 'احتمالاً $date1', fr = 'peut-être $date1', hu = 'talán $date1', mk = 'можеби $date1', ml = 'മിക്കവാറും $date1', nl = 'mogelijk $date1', pl = 'być może $date1', pt = 'possivelmente $date1', ro = 'poate $date1', ru = 'возможно $date1', sv = 'möjligen $date1', uk = 'можливо $date1', default='<span style="color:red">possibly $date1</span>', }, presumably = { -- see Q18122778 nDates = 1, ast = 'presumiblemente $date1', de = 'vermutlich $date1', en = 'presumably $date1', ['en-gb']='presumably $date1', ['en-ca']='presumably $date1', fr = 'présumé $date1', it = 'presumibilmente $date1', ml = 'ഊഹപ്രകാരം $date1', ru = 'предположительно $date1', pl = 'przypuszczalnie $date1', uk = 'здогадно $date1', default='<span style="color:red">presumably $date1</span>', }, ['year unknown']= { nDates = 0, ca='Any desconegut', ['be-tarask']='Год невядомы', bn='অজানা বছর', cs='neznámý rok', de='Jahr unbekannt', el='άγνωστο έτος', en='year unknown', ['en-gb']='year unknown', ['en-ca']='year unknown', eo='jaro nesciata', es='Año desconocido', fa='سال نامعلوم', fi='tuntematon vuosi', fr='Année inconnue', he='שנה לא ידועה', hsb='lěto njeznate', hr='nepoznata godina', it='anno sconosciuto', ja='年代不明', la='Sine anno', ml='വർഷം ലഭ്യമല്ല', mk='непозната година', nb='ukjent år', nn='ukjent år', nl='zonder jaar', no='ukjent år', pl='rok nieznany', qu='Mana riqsisqa wata', ru='Год неизвестен', sk='neznámy rok', sl='neznano leto', sv='Okänt år', uk='рік невідомий', ur='سال نامعلوم', vi='không rõ năm', ['zh-hans']='年份未知', ['zh-hant']='年份未知', default='<span style="color:red">year unknown</span>', }, -- ========================================== -- === Parts of a whole ===================== -- ========================================== ['firsthalf'] = { nDates = 1, ar='النصف الأول من $date1', ['be-tarask']={'першая палова $date1', case={'gen',''}}, bn='$date1-এর প্রথমার্ধ', ca='1a meitat de $date1', cs={'1. polovina $date1', case={'gen',''}}, da='1. halvdel af $date1', de='1. Hälfte $date1', el='πρώτο μισό του $date1', en='first half of $date1', ['en-gb']='first half of $date1', ['en-ca']='first half of $date1', eo='unua duono de $date1', es='primera mitad de $date1', et='$date1 esimene pool', fa='نیمهٔ نخست $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 alkupuolisko' end, case={'gen',''}}, fr='1<sup>re</sup> moitié de $date1', gl='primeira metade de $date1', he='במחצית הראשונה של $date1', hsb={'prěnja połojca $date1', case={'gen',''}}, hu='$date1 első felében', hr='prva polovina $date1', it='prima metà del $date1', ja='$date1上半期', mk='прва половина на $date1', ml='$date1 ആദ്യപകുതി', nb='første halvdel av $date1', nn='fyrste halvdelen av $date1', nds='1. Helft $date1', nl='eerste helft $date1', no='første halvdel av $date1', pl={'pierwsza połowa $date1', case={'gen',''}}, pt='primeira metade de $date1', qu='$date1 ñawpaq kuskanpi', ro='prima jumătate a lui $date1', ru={'первая половина $date1', case={'gen',''}}, sk={'1. polovica $date1', case={'gen',''}}, sl={function(date1, state) return 'prva polovica ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='första halvan av $date1', uk={'перша половина $date1', case={'gen',''}}, ur='نصف اول $date1', vi='nửa đầu năm $date1', ['zh-hans']='$date1上半期', ['zh-hant']='$date1上半期', default='<span style="color:red">first half of $date1</span>', }, ['secondhalf'] = { nDates = 1, ar='النصف الثاني من $date1', ['be-tarask']={'другая палова $date1', case={'gen',''}}, bn='$date1-এর দ্বিতীয়ার্ধ', ca='2a meitat de $date1', cs={'2. polovina $date1', case={'gen',''}}, da='2. halvdel af $date1', de='2. Hälfte $date1', el='δεύτερο μισό του $date1', en='second half of $date1', ['en-gb']='second half of $date1', ['en-ca']='second half of $date1', eo='dua duono de $date1', es='segunda mitad de $date1', et='$date1 teine pool', fa='نیمهٔ دوم $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 loppupuolisko' end, case={'gen',''}}, fr='2<sup>e</sup> moitié de $date1', gl='segunda metade de $date1', he='במחצית השנייה של $date1', hsb={'druha połojca $date1', case={'gen',''}}, hr='druga polovina $date1', hu='$date1 második felében', it='seconda metà del $date1', ja='$date1下半期', mk='втора половина на $date1', ml='$date1 രണ്ടാം പകുതി', nb='andre halvdel av $date1', nn='andre halvdelen av $date1', nds='2. Helft vun $date1', nl='tweede helft $date1', no='andre halvdel av $date1', pl={'druga połowa $date1', case={'gen',''}}, pt='segunda metade de $date1', qu='$date1 qhipaq kuskanpi', ro='a doua jumătate a lui $date1', ru={'вторая половина $date1', case={'gen',''}}, sk={'2. polovica $date1', case={'gen',''}}, sl={'druga polovica {{IfNum | $date0 | leta}} $date1', case={'gen',''}}, sv='andra halvan av $date1', uk={'друга половина $date1', case={'gen',''}}, ur='نصف آخر $date1', vi='nửa cuối năm $date1', ['zh-hans']='$date1下半期', ['zh-hant']='$date1下半期', default='<span style="color:red">second half of $date1</span>', }, ['1quarter']= { nDates = 1, bn='$date1-এর প্রথম ত্রৈমাসিক', ['be-tarask']={'першая чвэрць $date1', case={'gen',''}}, cs={'1. čtvrtina $date1', case={'gen',''}}, de='erstes Viertel $date1', en='first quarter of $date1', ['en-gb']='first quarter of $date1', ['en-ca']='first quarter of $date1', eo='unua kvarono de $date1', es='primer cuarto del $date1', fa='ربع نخست $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 ensimmäinen neljännes' end, case={'gen',''}}, fr= function(date1, state) return 'premier quart '.. frOf[state.precision] ..' $date1' end, he='רבעון ראשון של $date1', hsb={'prěnja štwórćina $date1', case={'gen',''}}, hr='prva četvrtina $date1', it='primo quarto del $date1', ja='$date1第1四半期', ml='$date1 ഒന്നാം ത്രൈമാസം', nb='første kvartal av $date1', nl='eerste kwart $date1', nn='fyrste kvartalet av $date1', no='første kvartal av $date1', pl={'pierwsza ćwiartka $date1', case={'gen',''}}, qu='$date1 ñawpaq ñiqin tawa phatmanpi', ru={'первая четверть $date1', case={'gen',''}}, sk={'1. štvrtina $date1', case={'gen',''}}, sl={'prva četrtina {{IfNum | $date0 | leta}} $date1', case={'gen',''}}, sl={function(date1, state) return 'prva četrtina ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='första kvartalet av $date1', uk={'1-ша чверть $date1', case={'gen',''}}, ur='پہلی سہ ماہی $date1', vi='quý 1 năm $date1', ['zh-hans']='$date1第一季度', ['zh-hant']='$date1第一季度', default='<span style="color:red">first quarter of $date1</span>', }, ['2quarter']= { nDates = 1, ['be-tarask']={'другая чвэрць $date1', case={'gen',''}}, bn='$date1-এর দ্বিতীয় ত্রৈমাসিক', cs={'2. čtvrtina $date1', case={'gen',''}}, de='zweites Viertel $date1', en='second quarter of $date1', ['en-gb']='second quarter of $date1', ['en-ca']='second quarter of $date1', eo='dua kvarono de $date1', es='segundo cuarto del $date1', fa='ربع دوم $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 toinen neljännes' end, case={'gen',''}}, fr= function(date1, state) return 'deuxième quart '.. frOf[state.precision] ..' $date1' end, he='רבעון שני של $date1', hsb={'druha štwórćina $date1', case={'gen',''}}, hr='druga četvrtina $date1', it='secondo quarto del $date1', ja='$date1第2四半期', ml='$date1 രണ്ടാം ത്രൈമാസം', nb='andre kvartal av $date1', nl='tweede kwart $date1', nn='andre kvartalet av $date1', no='andre kvartal av $date1', pl={'druga ćwiartka $date1', case={'gen',''}}, qu='$date1 iskay ñiqin tawa phatmanpi', ru={'вторая четверть $date1', case={'gen',''}}, sk={'2. štvrtina $date1', case={'gen',''}}, sl={function(date1, state) return 'druga četrtina ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='andra kvartalet av $date1', uk={'2-га чверть $date1', case={'gen',''}}, ur='دوسری سہ ماہی $date1', vi='quý 2 năm $date1', ['zh-hans']='$date1第二季度', ['zh-hant']='$date1第二季度', default='<span style="color:red">second quarter of $date1</span>', }, ['3quarter']= { nDates = 1, ['be-tarask']={'трэцяя чвэрць $date1', case={'gen',''}}, bn='$date1-এর তৃতীয় ত্রৈমাসিক', cs={'3. čtvrtina $date1', case={'gen',''}}, de='drittes Viertel $date1', en='third quarter of $date1', ['en-gb']='third quarter of $date1', ['en-ca']='third quarter of $date1', eo='tria kvarono de $date1', es='tercer cuarto del $date1', fa='ربع سوم $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 kolmas neljännes' end, case={'gen',''}}, fr= function(date1, state) return 'troisième quart '.. frOf[state.precision] ..' $date1' end, he='רבעון שלישי של $date1', hsb={'třeća štwórćina $date1', case={'gen',''}}, hr='treća četvrtina $date1', it='terzo quarto del $date1', ja='$date1第3四半期', ml='$date1 മൂന്നാം ത്രൈമാസം', nb='tredje kvartal av $date1', nl='derde kwart $date1', nn='tredje kvartalet av $date1', no='tredje kvartal av $date1', pl={'trzecia ćwiartka $date1', case={'gen',''}}, qu='$date1 kimsa ñiqin tawa phatmanpi', sk={'3. štvrtina $date1', case={'gen',''}}, sl={function(date1, state) return 'tretja četrtina ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, ru={'третья четверть $date1', case={'gen',''}}, sv='tredje kvartalet av $date1', uk={'3-тя чверть $date1', case={'gen',''}}, ur='تیسری سہ ماہی $date1', vi='quý 3 năm $date1', ['zh-hans']='$date1第三季度', ['zh-hant']='$date1第三季度', default='<span style="color:red">third quarter of $date1</span>', }, ['4quarter']= { nDates = 1, ['be-tarask']={'чацьвёртая чвэрць $date1', case={'gen',''}}, bn='$date1-এর চতুর্থ ত্রৈমাসিক', cs={'4. čtvrtina $date1', case={'gen',''}}, de='letztes Viertel $date1', en='fourth quarter of $date1', ['en-gb']='fourth quarter of $date1', ['en-ca']='fourth quarter of $date1', eo='kvara kvarono de $date1', es='último cuarto del $date1', fa='ربع چهارم $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 viimeinen neljännes' end, case={'gen',''}}, fr= function(date1, state) return 'quatrième quart '.. frOf[state.precision] ..' $date1' end, he='רבעון רביעי של $date1', hsb={'poslednja štwórćina $date1', case={'gen',''}}, hr='četvrta četvrtina $date1', it='quarto quarto del $date1', ja='$date1第4四半期', ml='$date1 നാലാം ത്രൈമാസം', nb='fjerde kvartal av $date1', nl='vierde kwart $date1', nn='tredje kvartalet av $date1', no='fjerde kvartal av $date1', pl={'czwarta ćwiartka $date1', case={'gen',''}}, qu='$date1 qhipaq ñiqin tawa phatmanpi', ru={'последняя четверть $date1', case={'gen',''}}, sk={'4. štvrtina $date1', case={'gen',''}}, sl={function(date1, state) return 'zadnja četrtina ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='fjärde kvartalet av $date1', uk={'4-та чверть $date1', case={'gen',''}}, ur='چوتھی سہ ماہی $date1', vi='quý 4 năm $date1', ['zh-hans']='$date1第四季度', ['zh-hant']='$date1第四季度', default='<span style="color:red">forth quarter of $date1</span>', }, -- ========================================== -- === Seasons ============================== -- ========================================== ['spring'] = { nDates = 1, ar='ربيع $date1', ['be-tarask']='вясна $date1', bn='বসন্তকাল $date1', ca='primavera $date1', cs={'jaro $date1', case={'gen', ''}}, da='Forår $date1', de='Frühling $date1', en='Spring $date1', ['en-gb']='Spring $date1', ['en-ca']='Spring $date1', eo='printempo $date1', es='primavera de $date1', et='$date1 kevad', fa='بهار $date1', fi='Kevät $date1', fr='printemps $date1', gl='primavera $date1', he='אביב $date1', hr='proljeće $date1', hsb='nalěćo $date1', hu='$date1 tavasza', it='primavera $date1', ja='{{#if:$date1|$date1年}}春', mk='пролет $date1', ml='$date1 വസന്തം', nb='våren $date1', nds='Vörjohr $date1', nn='våren $date1', nl='voorjaar $date1', no='våren $date1', pl='wiosna $date1', pt='primavera de $date1', ro='primăvara lui $date1', ru='весна $date1', sk={'jar $date1', case={'gen', ''}}, sl='pomlad $date1', sv='våren $date1', uk='весна $date1', ur='بہار $date1', vi='mùa đông năm $date1', ['zh-hans']='{{#if:$date1|$date1年}}春季', ['zh-hant']='{{#if:$date1|$date1年}}春季', default='<span style="color:red">Spring $date1</span>', }, ['summer'] = { nDates = 1, ar='صيف $date1', ['be-tarask']='лета $date1', bn='গ্রীষ্মকাল $date1', ca='estiu $date1', cs={'léto $date1', case={'gen', ''}}, da='Sommer $date1', de='Sommer $date1', en='Summer $date1', ['en-gb']='Summer $date1', ['en-ca']='Summer $date1', eo='somero $date1', es='verano de $date1', et='$date1 suvi', fa='تابستان $date1', fi='Kesä $date1', fr='été $date1', gl='verán $date1', he='קיץ $date1', hr='ljeto $date1', hsb='lěćo $date1', hu='$date1 nyara', it='estate $date1', ja='{{#if:$date1|$date1年}}夏', mk='лето $date1', ml='$date1 വേനൽ', nb='sommeren $date1', nds='Sommer $date1', nn='sumaren $date1', nl='zomer $date1', no='sommeren $date1', pl='lato $date1', pt='verão de $date1', ro='vara lui $date1', ru='лето $date1', sk={'leto $date1', case={'gen', ''}}, sl='poletje $date1', sv='sommaren $date1', uk='літо $date1', ur='گرما $date1', vi='mùa hè năm $date1', ['zh-hans']='{{#if:$date1|$date1年}}夏季', ['zh-hant']='{{#if:$date1|$date1年}}夏季', default='<span style="color:red">Summer $date1</span>', }, ['autumn'] = { nDates = 1, ar='خريف $date1', ['be-tarask']='восень $date1', bn='শরত্কাল $date1', ca='tardor $date1', cs={'podzim $date1', case={'gen', ''}}, da='Efterår $date1', de='Herbst $date1', en='Autumn $date1', ['en-gb']='Autumn $date1', ['en-ca']='Autumn $date1', eo='aŭtuno $date1', es='otoño de $date1', et='$date1 sügis', fa='پاییز $date1', fi='Syksy $date1', fr='automne $date1', gl='outono $date1', he='סתיו $date1', hr='jesen $date1', hsb='nazyma $date1', hu='$date1 ősze', it='autunno $date1', ja='{{#if:$date1|$date1年}}秋', mk='есен $date1', ml='$date1 ശരത്കാലം', nb='høsten $date1', nds='Harvst $date1', nn='hausten $date1', nl='herfst $date1', no='høsten $date1', pl='jesień $date1', pt='outono de $date1', ro='toamna lui $date1', ru='осень $date1', sk={'jeseň $date1', case={'gen', ''}}, sl='jesen $date1', sv='hösten $date1', uk='осінь $date1', ur='خزاں $date1', vi='mùa thu năm $date1', ['zh-hans']='{{#if:$date1|$date1年}}秋季', ['zh-hant']='{{#if:$date1|$date1年}}秋季', default='<span style="color:red">Autumn $date1</span>', }, ['winter'] = { nDates = 1, ar='شتاء $date1', ['be-tarask']='зіма $date1', bn='শীতকাল $date1', ca='hivern $date1', cs={'zima $date1', case={'gen', ''}}, da='Vinter $date1', de='Winter $date1', en='Winter $date1', ['en-gb']='Winter $date1', ['en-ca']='Winter $date1', eo='vintro $date1', es='invierno de $date1', et='$date1 talv', fa='زمستان $date1', fi='Talvi $date1', fr='hiver $date1', gl='inverno $date1', he='חורף $date1', hr='zima $date1', hsb='zyma $date1', hu='$date1 tele', it='inverno $date1', ja='{{#if:$date1|$date1年}}冬', mk='зима $date1', ml='$date1 ശൈത്യകാലം', nds='Winter $date1', nl='winter $date1', nb='vinteren $date1', nn='vinteren $date1', no='vinteren $date1', pl='zima $date1', pt='inverno de $date1', ro='iarna lui $date1', ru='зима $date1', sk={'zima $date1', case={'gen', ''}}, sl='zima $date1', sv='vintern $date1', uk='зима $date1', ur='سرما $date1', vi='mùa đông năm $date1', ['zh-hans']='{{#if:$date1|$date1年}}冬季', ['zh-hant']='{{#if:$date1|$date1年}}冬季', default='<span style="color:red">Winter $date1</span>', }, -- ========================================== -- === Decade/Century/Millennium ============ -- ========================================== ['decade'] = { nDates = 1, ar='أعوام $date1', ['be-tarask']='$date1-я гады', bn='$date1-এর দশক', ca='dècada del $date1', cs=function(date1, state) return ((date1 % 100 == 0) and 'první dekáda' or ((date1 % 100) .. '. léta')) .. ' ' .. (math.floor(date1 / 100) + 1) .. '. století' end, da="$date1'erne", de='$date1er-Jahre', el='δεκαετία του $date1', en='$date1s', ['en-gb']='$date1s', ['en-ca']='$date1s', eo='$date1-aj jaroj', es='años $date1', et='$date1ndad', fa='دههٔ $date1', fi='$date1-luvulla', fr='années $date1', gl='anos $date1', he='שנות ה-$date1', hr='$date1-ih', hsb='$date1te lěta', hu='$date1-s évek', it="{{#ifexpr: $date1 mod 100 = 0 | primo decennio del {{Roman|{{#expr: ($date1 / 100) + 1}}}} sec. | anni '{{#expr: $date1 mod 100 }} del {{Roman|{{#expr: floor($date1 / 100) + 1}}}} sec.}}", ja='$date1年代', mk='$date1-ти', ml='ദശകം $date1-ൽ', nb='$date1-årene', nn='$date1-åra', nds='Johren $date1', nl='jaren $date1', no='$date1-årene', pl=function(date1, state) local str = 'lata '..(date1 % 100)..'. ' if (date1 % 100 == 0) then str = 'pierwsza dekada ' elseif (date1 % 100 == 10) then str = 'druga dekada ' end return str .. Roman(math.floor(date1 / 100) + 1) .. ' wieku' end, pt='década de $date1', qu='$date1 watakuna', ro='anii $date1', ru='$date1-е годы', sk=function(date1, state) return ((date1 % 100 == 0) and 'prvé desaťročie' or ((date1 % 100) .. '. roky')) .. ' ' .. (math.floor(date1 / 100) + 1) .. '. storočia' end, sl='$date1-ta leta', sv='$date1-talet{{#switch:$date1|1300|1400|1500|1600|1700|1800|1900|2000|2100=&nbsp;(decennium)}}', uk='$date1-ті роки', ur='دہائی $date1', vi='thập niên $date1', ['zh-hans']='$date1年代', ['zh-hant']='$date1年代', default='<span style="color:red">$date1s</span>', }, ['century'] = { nDates = 1, ar='القرن $date1', ['be-tarask']=function(date1, state) return Ordinal(date1, 'be-tarask') .. ' стагодзьдзе' end, bn=function(date1, state) return Ordinal(date1, 'bn') .. ' শতাব্দী' end, ca='segle {{small-caps|{{lc:{{Roman year|$date1}}}}}}', cs='$date1. století', da='$date1. århundrede', de='$date1. Jahrhundert', el='$date1ος αιώνας', en=function(date1, state) return Ordinal(date1, 'en') .. ' century' end, ['en-gb']=function(date1, state) return Ordinal(date1, 'en-gb') .. ' century' end, ['en-ca']=function(date1, state) return Ordinal(date1, 'en-ca') .. ' century' end, eo='$date1-a jarcento', es=function(date1, state) return 'siglo ' .. Roman(date1) end, et='$date1. sajand', fa='سدهٔ $date1', fi='{{#expr: ($date1 - 1) * 100}}-luvulla', fr=function(date1, state) return Ordinal(date1, 'fr', 'roman', 'm') .. ' siècle' end, gl=function(date1, state) return 'século ' .. Roman(date1) end, he='המאה ה-$date1', hr='$date1. stoljeće', hsb='$date1. lětstotk', hu='$date1. évszázad', it=function(date1, state) return Roman(date1) .. ' sec.' end, ja='$date1世紀', la='saeculum $date1', mk=function(date1, state) return Roman(date1) .. ' век' end, ml='$date1-ആം നൂറ്റാണ്ട്', nb=function(date1, state) return Ordinal(date1, 'nb') .. ' århundre' end, nn=function(date1, state) return 'det ' .. Ordinal(date1, 'no') .. ' århundre' end, nds='$date1. Johrhunnert', nl='$date1e eeuw', no=function(date1, state) return Ordinal(date1, 'no') .. ' århundre' end, pl=function(date1, state) return Roman(date1) .. ' ' .. decCentury('pl',state.case) end, pt=function(date1, state) return 'século ' .. Roman(date1) end, qu='$date1 ñiqin pachakwata', ro='secolul al $date1-lea', ru=function(date1, state) return Roman(date1) .. ' век' end, sk='$date1. storočie', sl=function(date1, state) return Ordinal(date1, 'sl') .. '. stoletje' end, sv='{{#expr:$date1 - 1}}00-talet', tt=function(date1, state) return Roman(date1) .. ' гасыр' end, uk=function(date1, state) return Roman(date1) .. ' ст.' end, ur='صدی $date1', vi='thế kỷ $date1', ['zh-hans']='$date1世纪', ['zh-hant']='$date1世紀', default='<span style="color:red">$date1 century</span>', }, ['millennium'] = { nDates = 1, ['be-tarask']=function(date1, state) return Ordinal(date1, 'be-tarask') .. ' тысячагодзьдзе' end, bn=function(date1, state) return Ordinal(date1, 'bn') .. ' সহস্রাব্দ' end, cs='$date1. tisíciletí', de='$date1. Jahrtausend', en=function(date1, state) return Ordinal(date1, 'en') .. ' millennium' end, ['en-gb']=function(date1, state) return Ordinal(date1, 'en-gb') .. ' millennium' end, ['en-ca']=function(date1, state) return Ordinal(date1, 'en-ca') .. ' millennium' end, eo='$date1-a jarmilo', es=function(date1, state) return Roman(date1) .. ' milenio' end, fa=function(date1, state) return 'هزارهٔ ' .. Ordinal(date1, 'fa') end, fr=function(date1, state) return Ordinal(date1, 'fr', 'roman', 'm') .. ' millénaire' end, hr='$date1. tisućljeće', hsb='$date1. lěttysac', it=function(date1, state) return Roman(date1) .. ' millennio' end, ja='$date1千年紀', la='millennium $date1', ml='$date1-മത്തെ സഹസ്രാബ്ദം', nb=function(date1, state) return Ordinal(date1, 'nb') .. ' årtusen' end, nn=function(date1, state) return Ordinal(date1, 'no') .. ' tusenår' end, nl='$date1e millennium', no=function(date1, state) return Ordinal(date1, 'no') .. ' årtusen' end, pl=function(date1, state) return Roman(date1) .. ' ' .. decMillennium('pl',state.case) end, pt=function(date1, state) return Roman(date1) .. ' milénio' end, qu='$date1 ñiqin waranqawata', ru=function(date1, state) return Roman(date1) .. ' тысячелетие' end, sk='$date1. tisícročie', sl=function(date1, state) return Ordinal(date1, 'sl') .. '. tisočletje' end, sv=function(date1, state) return Ordinal(date1, 'sv') .. ' årtusendet' end, uk=function(date1, state) return Roman(date1) .. ' тис.' end, ur=function(date1, state) return 'ہزارہ ' .. Ordinal(date1, 'ur') end, vi='thiên niên kỷ $date1', ['zh-hans']=function(date1, state) return Ordinal(date1, 'zh-hans') .. '千年' end, ['zh-hant']=function(date1, state) return Ordinal(date1, 'zh-hant') .. '千年' end, --default='{{Please translate|$date1 millennium|Module:I18n/complex date}}', default='<span style="color:red">$date1 millennium</span>', }, ['million'] = { de = '$date1 Millionen Jahre', en = '$date1 million years', ['en-gb'] = '$date1 million years', ['en-ca'] = '$date1 million years', eo = '$date1 milionoj da jaroj', es = '$date1 millones de años', nl = '$date1e miljoen jaar', fr = '$date1 millions d’années', it = '$date1 milioni di anni', ml = function(date1, state) if date1 >= 10 then return (date1 / 10) .. ' കോടി വർഷങ്ങൾ' -- 100 lakh = 1 crore else return (date1 * 10) .. ' ലക്ഷം വർഷങ്ങൾ' end end, nb = '$date1 millioner år', nl = '$date1 miljoen jaar', nn = '$date1 millionar år', pl = '$date1 milionów lat', pt = '$date1 milhões de anos', ru = '$date1 миллиона лет', uk = '$date1 мільйони років', ur = '$date1 ملین سال', ['zh-hans'] = '$date1百万年', ['zh-hant'] = '$date1百万年', default='<span style="color:red">$date1 million years</span>', nDates = 1, }, ['billion'] = { de = '$date1 Milliarden Jahre', en = '$date1 billion years', ['en-gb'] = '$date1 billion years', ['en-ca'] = '$date1 billion years', es = '$date1 mil millones de años', nl = '$date1e miljard jaar', fr = '$date1 milliard d’années', it = '$date1 miliardi di anni', ml = function(date1, state) return (date1 * 10) .. ' കോടി വർഷങ്ങൾ' end, nb = '$date1 milliarder år', nl = '$date1 miljard jaar', nn = '$date1 milliardar år', pl = '$date1 miliardów lat', pt = '$date1 mil milhões de anos', ru = '$date1 миллиардов лет', uk = '$date1 мільярдів років', ur = '$date1 بلین سال', ['zh-hans'] = '$date1十亿年', ['zh-hant'] = '$date1十億年', default='<span style="color:red$date1 billion years</span>', nDates = 1, }, -- ========================================== -- === Complex phrases ====================== -- ========================================== -- Phrases which for some languages can be build out of simpler phrases -- Their "default" state, used when no translation in the requested language was found, returns a "state" like -- table which is used to call the function again and built the phrase out of simple phrases -- $date1 - original date - 1 -- $date2 - original date -- if era is BC than $date1 and $date2 are swapped ['turn of the century'] = { nDates = 2, ['be-tarask']=function(date1, date2, state) return 'канец ' .. Roman(date1) ..' — пачатак ' .. Roman(date2) ..' стагодзьдзя' end, bn=function(date1, date2, state) return '$date1/' .. Ordinal(date2, 'bn') .. ' শতাব্দীর পালাবদলে' end, cs='přelom $date1. a $date2. století', de=function(date1, date2, state) return 'Jahrhundertwende $date1/' .. Ordinal(date2, 'de') end, en=function(date1, date2, state) return 'turn of the $date1/' .. Ordinal(date2, 'en') .. ' century' end, ['en-gb']=function(date1, date2, state) return 'turn of the $date1/' .. Ordinal(date2, 'en-gb') .. ' century' end, ['en-ca']=function(date1, date2, state) return 'turn of the $date1/' .. Ordinal(date2, 'en-ca') .. ' century' end, eo='ŝanĝo de la $date1-a al la $date2-a jarcentoj', es=function(date1, date2, state) return 'hacia finales del siglo ' .. Roman(date1) .. ' y principios del siglo ' .. Roman(date2) .. '' end, fi='{{#expr: $date1 * 100-100}}-luvun loppu ja {{#expr: $date2 * 100-100}}-luvun alku', fr=function(date1, date2, state) return 'fin du ' .. Ordinal(date1, 'fr', 'roman', 'm') .. ' siècle / début du ' .. Ordinal(date2, 'fr', 'roman', 'm') end, hr='kraj $date1. i početak $date2. stoljeća', hsb=function(date1, date2, state) return 'přechod ' .. Roman(date1) ..' do ' .. Roman(date2) ..' lětstotka' end, it=function(date1, date2, state) return 'fine del ' .. Roman(date1) .. ' sec. e inizio del ' .. Roman(date2) .. ' sec.' end, ja='$date1世紀から$date2世紀への変わり目', ml='$date1 നൂറ്റാണ്ട് അവസാനവും $date2 നൂറ്റാണ്ട് ആദ്യവും', nb=function(date1, date2, state) return 'slutten av det ' .. Ordinal(date1, 'nb') .. ' århundre/begynnelsen av det ' .. Ordinal(date2, 'nb') .. ' århundre' end, nl=function(date1, date2, state) return 'eind $date1e/begin $date2e eeuw' end, nn=function(date1, date2, state) return 'slutten av det ' .. Ordinal(date1, 'nn') .. ' hundreåret/byrjinga av det ' .. Ordinal(date2, 'nn') .. ' hundreåret' end, pl=function(date1, date2, state) return 'przełom ' .. Roman(date1) ..' i ' .. Roman(date2) ..' wieku' end, qu="$date1 ñiqin waranqawatamanta $date2 ñiqin waranqawataman t'ikrasqanpi", ru=function(date1, date2, state) return 'конец ' .. Roman(date1) ..' — начало ' .. Roman(date2) ..' века' end, sk='prelom $date1. a $date2. storočia', sl=function(date1, date2, state) return 'pozno ' .. Ordinal(date1, 'sl') .. '. in zgodnje ' .. Ordinal(date2, 'sl') .. '. stoletje' end, sv=function(date1, date2, state) return '$date1/' .. Ordinal(date2, 'sv') .. ' årtusendets sekelskifte' end, uk=function(date1, date2, state) return 'кін. ' .. Roman(date1) ..' ст. — поч. ' .. Roman(date2) ..' ст.' end, vi='bước sang thế kỷ $date2', ['zh-hans']=function(date1, date2, state) return '$date1世纪末' .. Ordinal(date2, 'zh-hans') .. '世纪初' end, ['zh-hant']=function(date1, date2, state) return '$date1世紀末' .. Ordinal(date2, 'zh-hant') .. '世紀初' end, default=function(date1, date2, state) return {conj='and', adj1 = 'late', unit1='century', era1=state.era1, adj2 = 'early', unit2='century', era2=state.era2} end, }, -- $date1 - original date - 10 -- $date2 - original date -- if era is BC than $date1 and $date2 are swapped ['turn of the decade']={ nDates = 2, ['be-tarask']='канец $date1-х — пачатак $date2-х гг.', bn='$date1-এর দশকের শেষে এবং $date2-এর দশকের শুরুতে', cs=function(date1, date2, state) local decade1 = math.floor((date1 % 100) / 10) local decade2 = math.floor((date2 % 100) / 10) local century1 = math.floor( date1 / 100) + 1 local century2 = math.floor( date2 / 100) + 1 if decade1 == 0 and date1 < date2 then return "přelom 1. a 2. dekády " .. century1 .. ". století" elseif decade2 == 0 and date2 < date1 then return "přelom 9. a 10. dekády " .. century1 .. ". století" elseif (decade1 == 9 and date1 < date2) or (decade2 == 9 and date2 < date1) then return "přelom " .. century1 .. ". a " .. century2 .. ". století" else return "přelom " .. decade1 .. "0. a " .. decade2 .. "0. let " .. century1 .. ". století" end end, de='späte $date1er und frühe $date2er', en='late $date1s and early $date2s', ['en-gb']='late $date1s and early $date2s', ['en-ca']='late $date1s and early $date2s', eo='ŝanĝo de la $date1-a al la $date2-a jardekoj', es='finales de los años $date1 y principios de los años $date2', fa='اواخر دههٔ $date1 و اوایل دههٔ $date2', fi='$date1-luvun loppu ja $date2-luvun alku', fr='fin des années $date1 / début des années $date2', hr='kraj $date1-ih i početak $date2-ih', hsb='pózdnje $date1te a zažne $date2te lěta', it='fine degli anni $date1 / inizio degli anni $date2', ja='$date1年代末から$date2年代初頭', ml='$date1കൾ അവസാനവും $date2കൾ ആദ്യവും', nb = 'sent på $date1-tallet og tidlig på $date2-tallet', nl = 'eind $date1e/begin $date2e decennium', nn = 'seint på $date1-talet og tidleg på $date2-talet', pl=function(date1, date2, state) local decade1 = math.floor(date1 % 100) local decade2 = math.floor(date2 % 100) local century1 = Roman(math.floor( date1 / 100) + 1) local century2 = Roman(math.floor( date2 / 100) + 1) if decade1 == 0 and date1 < date2 then return "przełom pierwszej i drugiej dekady " .. century1 .. " wieku" elseif decade1 == 10 and date1 < date2 then return "przełom drugiej dekady i lat 20. " .. century1 .. " wieku" elseif decade1 == 90 and decade2 == 0 then return "przełom ostatniej dekady " .. century1 .. " i pierwszej dekady " .. century2 .. " wieku" else return "przełom lat " .. decade1 .. ". i " .. decade2 .. ". " .. century1 .. " wieku" end end, qu='qhipaq $date1 watakuna ñawpaq $date2 watakunapas', ru='конец $date1-х — начало $date2-х гг.', sv='sena $date1-talet och tidiga $date2-talet', sk=function(date1, date2, state) local decade1 = math.floor((date1 % 100) / 10) local decade2 = math.floor((date2 % 100) / 10) local century1 = math.floor( date1 / 100) + 1 local century2 = math.floor( date2 / 100) + 1 if decade1 == 0 and date1 < date2 then return "prelom 1. a 2. desaťročia " .. century1 .. ". storočia" elseif decade2 == 0 and date2 < date1 then return "prelom 9. a 10. desaťročia " .. century1 .. ". storočia" elseif (decade1 == 9 and date1 < date2) or (decade2 == 9 and date2 < date1) then return "prelom " .. century1 .. ". a " .. century2 .. ". storočia" else return "prelom " .. decade1 .. "0. a " .. decade2 .. "0. rokov " .. century1 .. ". storočia" end end, sl='pozna $date1-ta in zgodnja $date2-ta leta', uk='кінець $date1-х — початок $date2-х рр.', ur='اخیر دہائی $date1 و ابتدا دہائی $date2', vi='bước sang thập niên $date2', ['zh-hans']='$date1年代末$date2年代初', ['zh-hant']='$date1年代末$date2年代初', default=function(date1, date2, state) return {conj='and', adj1 = 'late', unit1='decade', era1=state.era1, adj2 = 'early', unit2='decade', era2=state.era2} end, }, -- $date1 - original date - 1 -- $date2 - original date -- if era is BC than $date1 and $date2 are swapped ['turn of the year']={ nDates = 2, ['be-tarask']='канец $date1 — пачатак $date2 г.', bn='$date1-এর শেষে এবং $date2-এর শুরুতে', cs='přelom roku $date1/$date2', de='Jahreswechsel $date1/$date2', en='late $date1 and early $date2', ['en-gb']='late $date1 and early $date2', ['en-ca']='late $date1 and early $date2', eo='jarŝanĝo $date1 / $date2', es='finales de $date1 y principios de $date2', fa='اواخر $date1 و اوایل $date2', fi='vuoden $date1 loppu ja vuoden $date2 alku', fr='fin $date1 / début $date2', hsb='kónc $date1/spočatk $date2', ja='$date1年末から$date2年初め', ml='$date1 അവസാനവും $date2 ആദ്യവും', nb='årsskiftet $date1/$date2', nl='eind $date1 en begin $date2', nn='årsskiftet $date1/$date2', pl='koniec $date1 i początek $date2 roku', qu="$date1 qhipaq p'unchawkuna $date2 ñawpaq p'unchawkunapas", ru='конец $date1 — начало $date2 г.', sk='prelom rokov $date1/$date2', sl='konec leta $date1 in začetek $date2', sv='sena $date1 och tidiga $date2', uk='кінець $date1 — початок $date2 р.', ur='اخیر $date1 و ابتدا $date2', vi='bước sang năm $date2', ['zh-hans']='$date1年末$date2年初', ['zh-hant']='$date1年末$date2年初', default=function(date1, date2, state) return {conj='and', adj1 = 'late', unit1='year', era1=state.era1, adj2 = 'early', unit2='year', era2=state.era2} end, }, ['circa2'] = { nDates = 2, ar='حوالي $date1 – $date2', ['be-tarask']={'прыкладна з $date1 да прыкладна $date2', case={'gen','gen'}}, bn='প্রায় $date1 এবং প্রায় $date2-এর মাঝামাঝি', ca='cap a $date1 o $date2', cs='cca $date1 až $date2', da='ca. mellem $date1 og $date2', de='etwa $date1 bis $date2', el='περίπου μεταξύ $date1 και $date2', en='between circa $date1 and circa $date2', ['en-gb']='between circa $date1 and circa $date2', ['en-ca']='between circa $date1 and circa $date2', eo='proksimume de $date1 ĝis $date2', es='hacia $date1 o $date2', et='ca. $date1 ja $date2 vahel', fa='حدوداً بین $date1 و $date2', fi={function(date1, date2, state) return 'suunnilleen ' .. decYear(state,'fi','xxx') .. '$date1 ja $date2 välisenä aikana' end, case={'gen','gen'}}, fr='vers $date1-$date2', gl='entre $date1 e $date2', he='בסביבות ה- $date1 עד $date2', hr='oko između $date1 i $date2', hsb={'wot něhdźe $date1 do něhdźe $date2', case={'gen','gen'}}, hu='$date1 és $date2 között', it='tra il $date1 e il $date2 circa', ja='$date1頃と$date2頃の間', mk='~ помеѓу $date1 и $date2', ml='ഉദ്ദേശം $date1-നും ഉദ്ദേശം $date2-നും ഇടയ്ക്ക്', nb='ca. mellom $date1 og $date2', nn='ca. mellom $date1 og $date2', nds='üm un bi $date1 bet $date2', nl='ca. $date1 tot $date2', no='ca. mellom $date1 og $date2', pl={'od około $date1 do około $date2', case={'gen','gen'}}, pt='cerca de $date1 a $date2', qu="$date1 p'unchawmantachá $date2 p'unchawkamachá", ro='circa $date1 – $date2', ru={'с около $date1 до, примерно, $date2', case={'gen','gen'}}, sk='cca $date1 až $date2', sl={function(date1, date2, state) return 'ok. ' .. decYear(state,'sl','gen') .. ' $date1 do $date2' end, case={'gen','gen'}}, sv='c:a $date1 till $date2', uk={'з близько $date1 до, приблизно, $date2', case={'gen','gen'}}, ur='قریباً بین $date1 و $date2', vi='giữa vào khoảng $date1 và vào khoảng $date2', ['zh-hans']='约$date1~$date2间', ['zh-hant']='約$date1~$date2間', default=function(date1, date2, state) return {conj='between', adj1 = 'circa', unit1=state.unit1, era1=state.era1, adj2 = 'circa', unit2=state.unit2, era2=state.era2} end, }, -- ========================================== -- === Eras and calendars =================== -- ========================================== ['julian'] = { nDates = 2, ar='$date1 ($date2 حسب [[w:ar:التقويم اليولياني|تقويم يولياني]])', ['be-tarask']='$date1 ($date2 паводле [[w:be-tarask:Юліянскі каляндар|ст. ст.]]', bn='$date1 (জুলিয়ান ক্যালেন্ডারে $date2)', ca='$date1 ($date2 segons el [[w:ca:Calendari julià|calendari julià]])', cs='$date1 ($date2 podle [[w:cs:Juliánský kalendář|juliánského kalendáře]])', da='$date1 ($date2 [[w:da:Julianske kalender|Julianske kalender]])', de='$date1 ($date2 [[w:de:Julianischer Kalender|Julianischer Kalender]])', el='$date1 ($date2 με το [[w:el:Ιουλιανό ημερολόγιο|Ιουλιανό ημερολόγιο]])', en='$date1 ($date2 in [[w:Old Style and New Style dates|Julian calendar]])', ['en-gb']='$date1 ($date2 in [[w:Old Style and New Style dates|Julian calendar]])', ['en-ca']='$date1 ($date2 in [[w:Old Style and New Style dates|Julian calendar]])', fa='$date1 ($date2 در [[w:fa:Old Style and New Style dates|تقویم جولیان]])', eo='$date1 ($date2 laŭ la [[w:eo:Julia kalendaro|Julia Kalendaro]])', es='$date1 ($date2 según el [[w:es:Calendario juliano|calendario juliano]])', et='$date1 ($date2 [[w:et:Juliuse kalender|Juliuse kalendris]])', fi='$date1 ($date2 [[w:fi:juliaaninen kalenteri|juliaanisen kalenterin]] mukaan)', fr='$date1 ($date2 dans le [[w:fr:calendrier julien|calendrier julien]])', gl='$date1 ($date2 segundo o [[w:gl:Calendario xuliano|calendario xuliano]])', hu='$date1 ($date2 a [[w:hu:Julianus-naptár|Julián naptárban]])', hr='$date1 ($date2 prema [[w:hr:Julijanski kalendar|julijanskom kalendaru]])', he='$date1 ($date2 ב[[w:he:לוח היוליאני|הלוח היוליאני]])', hsb='$date1 ($date2 po julianiskej protyce)', it='$date1 ($date2 secondo il [[w:it:Calendario giuliano|calendario giuliano]])', ja='$date1([[w:ja:ユリウス暦|ユリウス暦]] $date2)', mk='$date1 ($date2 по [[w:mk:Јулијански календар|Јулијанскиот календар]])', ml='$date1 ([[w:ml:ജൂലിയൻ കാലഗണനാരീതി|ജൂലിയൻ വർഷത്തിൽ]] $date2)', ml='$date1 ([[w:Old Style and New Style dates|ജൂലിയൻ കാലഗണനാരീതിയിൽ]] $date2)', nds='$date1 ($date2 na’n [[w:nds:Juliaansch Klenner|Juliaanschen Klenner]])', nb='$date1 ($date2 etter [[w:no:Juliansk kalender|den julianske kalenderen]])', nn='$date1 ($date2 etter [[w:no:Juliansk kalender|den julianske kalenderen]])', nl='$date1 ($date2 volgens de [[w:nl:Juliaanse kalender|Juliaanse kalender]])', no='$date1 ($date2 etter [[w:no:Juliansk kalender|den julianske kalender]])', pl='$date1 ($date2 wedle [[w:pl:Daty nowego i starego porządku|kalendarza juliańskiego]])', pt='$date1 ($date2 no [[w:pt:Mudança para o calendário gregoriano|calendário juliano]])', qu='$date1 ($date2 [[w:qu:Hulyanu kalindaryu|Hulyanu kalindaryupi]])', ro='$date1 ($date2 în [[w:ro:calendarul iulian|calendarul iulian]])', ru='$date1 ($date2 [[w:ru:Юлианский календарь|по ст. ст.]])', sk='$date1 ($date2 podľa [[w:sk:Juliánsky kalendár|juliánskeho kalendára]])', sl='$date1 ($date2 po [[w:sl:Julijanski koledar|julijanskem koledarju]])', sv='$date1 ($date2 [[w:sv:Gamla stilen|gamla stilen]])', uk='$date1 ($date2 [[w:uk:Юліанський календар|Юліанський календар]])', ur='$date1 ($date2 حسب [[w:ur:نئے اور پانے انداز کی تاریخ|جولین تقویم]])', vi='$date1 ($date2 trong [[w:vi:Lịch Julius|lịch Juliêng]])', ['zh-hans']='$date1([[w:Old Style and New Style dates|格里历]]$date2)', ['zh-hant']='$date1([[w:Old Style and New Style dates|格里曆]]$date2)', default='<span style="color:red">$date1 ($date2 in [[w:Old Style and New Style dates|Julian calendar]])</span>', }, ['ad'] = { nDates = 1, ['be-tarask']='$date1 [[w:be-tarask:ад Нараджэньня Хрыста|пасьля Н. Х.]]', bn ='$date1 খ্রিস্টাব্দ', ca ='$date1 dC', cs ='$date1 n. l.', de ='$date1 n. Chr.', el ='$date1 μ.Χ.', en ='$date1 [[w:en:Anno Domini|AD]]', ['en-gb'] ='$date1 [[w:en:Anno Domini|AD]]', ['en-ca'] ='$date1 [[w:en:Anno Domini|AD]]', eo ='$date1 pK', es ='$date1 [[w:es:Anno Dómini|d. C.]]', fa ='$date1 م.', fi ='$date1 jaa.', fr ='$date1 ap. J.-C.', gl ='$date1 d.C.', he ='$date1 לספירה', hu ='i. sz. $date1', hr ='$date1 p. Kr.', hsb='$date1 po Chr.', it ='$date1 d.C.', ja ='西暦$date1', mk ='$date1 н.е.', ml ='ക്രി.വ. $date1', nb ='$date1 e.Kr.', nl ='$date1 n.Chr.', nn ='$date1 e.Kr.', no ='$date1 e.Kr.', pl ='$date1 [[w:pl:naszej ery|n.e.]]', ro ='$date1 d.Hr.', ru ='$date1 н. э.', sk ='$date1 po Kr.', sl ='$date1 n. št.', sv ='$date1 [[w:sv:Efter Kristus|e.Kr.]]', uk ='$date1 н. е.', ur ='$date1 ء', vi ='$date1 [[w:vi:Công Nguyên|CN]]', ['zh-hans'] ='[[w:zh:基督纪年|公元]]$date1', ['zh-hant'] ='[[w:zh:基督纪年|公元]]$date1', default='<span style="color:red">$date1 [[w:en:Anno Domini|AD]]</span>', }, ['ah'] = { nDates = 1, ['be-tarask']='$date1 [[w:be-tarask:Ісламскі каляндар|хіджры]]', bn ='$date1 [[w:bn:ইসলামি বর্ষপঞ্জি|হিজরী]]', cs ='$date1 [[w:cs:Anno Hegirae|AH]]', en ='$date1 [[w:en:Hijri year|AH]]', ['en-gb'] ='$date1 [[w:en:Hijri year|AH]]', ['en-ca'] ='$date1 [[w:en:Hijri year|AH]]', eo ='$date1 post la [[w:eo:Heĝiro|Heĝiro]]', fa ='$date1 ه.ق.', fr ='$date1 [[w:fr:Calendrier musulman|A.H.]]', he ="$date1 [[w:he:הלוח המוסלמי|להג'רה]]", hr ='$date1 god. hidžre', ja ='[[w:ja:ヒジュラ暦|ヒジュラ暦]]$date1', ml ='$date1 [[w:ml:ഇസ്‌ലാമിക കലണ്ടർ|ഹിജ്റ]]', nb ='$date1 [[w:no:Den muslimske kalenderen|A.H.]]', nn ='$date1 [[w:nn:Den muslimske kalenderen|A.H.]]', pl ='$date1 [[w:pl:Kalendarz muzułmański|A.H.]]', ru ='$date1 [[w:ru:Исламский календарь|хиджры]]', sk ='$date1 [[w:sk:A. H.|A. H.]]', sv ='$date1 [[w:sv:Muslimska kalendern|A.H.]]', uk ='$date1 [[w:uk:Мусульманський календар|хіджри]]', ur ='$date1 ھ', vi ='$date1 [[w:vi:Lịch Hồi giáo|AH]]', ['zh-hans'] ='[[w:en:Hijri year|Hijri历]]$date1', ['zh-hant'] ='[[w:en:Hijri year|Hijri曆]]$date1', default='<span style="color:red">$date1 [[w:en:Hijri year|AH]]</span>', }, ['bc'] = { nDates = 1, ar ='$date1 ق.م.', ['be-tarask']='$date1 [[w:be-tarask:да Нараджэньня Хрыста|да Н. Х.]]', bn ='খ্রিস্টপূর্ব $date1', ca ='$date1 [[w:ca:Abans de Crist|aC]]', cs ='$date1 [[w:cs:Př. n. l.|př. n. l.]]', de ='$date1 [[w:de:v. Chr.|v. Chr.]]', el ='$date1 π.Χ.', en ='$date1 [[w:en:Ante Christum Natum|BC]]', ['en-gb'] ='$date1 [[w:en:Ante Christum Natum|BC]]', ['en-ca'] ='$date1 [[w:en:Ante Christum Natum|BC]]', eo ='$date1 aK', es ='$date1 [[w:es:Antes de Cristo|a. C.]]', fa ='$date1 پ.م.', fi ='$date1 eaa.', fr ='$date1 av. J.-C.', gl ='$date1 [[w:gl:Antes de Cristo|a.C.]]', he ='$date1 לפני הספירה', hr ='$date1 [[w:hr:Pr. Kr.|pr. Kr.]]', hsb='$date1 do Chr.', hu ='i. e. $date1', it ='$date1 [[w:it:Avanti Cristo|a.C.]]', ja ='[[w:ja:紀元前|紀元前]]$date1', mk ='$date1 п.н.е.', ml ='ക്രി.മു. $date1', nds='$date1 vör uns Tied', nb ='$date1 f.Kr.', nl ='$date1 v.Chr.', nn ='$date1 f.Kr.', no ='$date1 f.Kr.', pl ='$date1 [[w:pl:przed naszą erą|p.n.e.]]', ro ='$date1 î.Hr.', ru ='$date1 до н. э.', sk ='$date1 [[w:sk:Pred Kristom|pred Kr.]]', sl ='$date1 pr. n. št.', sv ='$date1 [[w:sv:Före Kristus|f.Kr.]]', uk ='$date1 до н. е.', ur ='$date1 ق م', vi ='$date1 [[w:vi:Trước Công Nguyên|TCN]]', ['zh-hans'] ='[[w:zh:基督纪年|公元前]]$date1', ['zh-hant'] ='[[w:zh:基督纪年|公元前]]$date1', default='<span style="color:red">$date1 [[w:en:Ante Christum Natum|BC]]</span>', }, ['bp'] = { nDates = 1, ar ='$date1 [[w:ar:قبل الحاضر|قبل الحاضر]]', be ='$date1 [[w:be-tarask:Да нашых дзён|Да нашых дзён]]', ['be-tarask']='$date1 [[w:be-tarask:Да нашых дзён|Да нашых дзён]]', bn ='বর্তমানের আগে $date1', ca ='$date1 [[w:ca:BP (unitat de temps)|BP]]', cs ='$date1 [[w:cs:B. P.|B. P.]]', de ='$date1 [[w:de:Before Present|BP]]', el ='$date1 [[w:en:Before Present|BP]]', en ='$date1 [[w:en:Before Present|BP]]', ['en-gb'] ='$date1 [[w:en:Before Present|BP]]', ['en-ca'] ='$date1 [[w:en:Before Present|BP]]', eo='$date1 [[w:eo:Before Present|BP]]', es ='$date1 [[w:es:Before Present|BP]]', et ='$date1 [[w:et:Before Present|BP]]', fa ='$date1 پیش از اکنون', fi ='$date1 [[w:fi:BP (ajanlasku)|BP]]', fr ='$date1 [[w:fr:Avant le présent|avant le présent]]', hr ='$date1 [[w:hr:BP|prije današnjice]]', it ='$date1 [[w:it:Before Present|BP]]', ja ='$date1 [[w:ja:BP (年代測定)|BP]]', lv ='$date1 [[w:lv:Pirms mūsdienām|Pirms mūsdienām]]', mk ='$date1 [[w:mk:Пред сегашноста|п.с.]]', ml ='[[w:en:Before Present|ഇന്നത്തെ കാലത്തിനു മുമ്പ്]] $date1', nb ='$date1 [[w:no:Before Present|BP]]', nn ='$date1 [[w:nn:Before Present|BP]]', nl ='$date1 [[w:nl:Before Present|BP]]', no ='$date1 [[w:no:Before Present|BP]]', pl ='$date1 [[w:pl:Before Present|BP]]', pt ='$date1 [[w:pt:Antes do Presente|Antes do Presente]]', ru ='$date1 [[w:ru:До настоящего времени|до н. в.]]', sk ='$date1 [[w:sk:BP (čas)|BP]]', sl ='$date1 [[w:sl:pred sedanjostjo|p. s.]]', sr ='$date1 [[w:sr:Прије садашњости|Прије садашњости]]', sv ='$date1 [[w:sv:B.P.|B.P.]]', uk ='$date1 [[w:uk:Років тому|Років тому]]', ur ='$date1 قبل حاضر', vi ='$date1 [[w:en:Before Present|trước ngày nay]]', ['zh-hans'] ='[[w:zh:距今|距今]]$date1', ['zh-hant'] ='[[w:zh:距今|距今]]$date1', default='<span style="color:red">$date1 [[w:en:Before Present|BP]]</span>', }, } -- List of possible string inputs to "adj", "junction", "era", and "precision" fields -- Each term is matched to its synonym and the role: a - adjective, e - era -- p - precision, c - calendar and combos, r - certainty and j - conjunction p.Synonyms = { -- adjectives and prepositions ['quarter1']={'1quarter','a'}, ['1stquarter']={'1quarter','a'}, ['1st quarter']={'1quarter','a'}, ['first quarter']={'1quarter','a'}, ['1quarter']={'1quarter','a'}, ['quarter2']={'2quarter','a'}, ['2ndquarter']={'2quarter','a'}, ['2nd quarter']={'2quarter','a'}, ['second quarter']={'2quarter','a'}, ['2quarter']={'2quarter','a'}, ['quarter3']={'3quarter','a'}, ['3rdquarter']={'3quarter','a'}, ['3rd quarter']={'3quarter','a'}, ['third quarter']={'3quarter','a'}, ['3quarter']={'3quarter','a'}, ['quarter4']={'4quarter','a'}, ['4thquarter']={'4quarter','a'}, ['4th quarter']={'4quarter','a'}, ['fourth quarter']={'4quarter','a'}, ['4quarter']={'4quarter','a'}, ['spring']={'spring','a'}, ['summer']={'summer','a'}, ['fall']={'autumn','a'}, ['autumn']={'autumn','a'}, ['winter']={'winter','a'}, ['beginning']={'early','a'}, ['early']={'early','a'}, ['1half']={'firsthalf','a'}, ['1sthalf']={'firsthalf','a'}, ['1st half']={'firsthalf','a'}, ['firsthalf']={'firsthalf','a'}, ['first half']={'firsthalf','a'}, ['end']={'late','a'}, ['late']={'late','a'}, ['middle']={'mid','a'}, ['mid']={'mid','a'}, ['2half']={'secondhalf','a'}, ['2ndhalf']={'secondhalf','a'}, ['2nd half']={'secondhalf','a'}, ['secondhalf']={'secondhalf','a'}, ['second half']={'secondhalf','a'}, ['<']={'before','a'}, ['b']={'before','a'}, ['before']={'before','a'}, ['>']={'after','a'}, ['a']={'after','a'}, ['after']={'after','a'}, ['~']={'circa','a'}, ['c']={'circa','a'}, ['ca']={'circa','a'}, ['circa']={'circa','a'}, ['by']={'by','a'}, ['from']={'from','a'}, ['until']={'until','a'}, ['uncertain']={'uncertain','r'}, ['possibly']={'possibly','r'}, ['probably']={'probably','r'}, ['presumably']={'presumably','r'}, -- eras ['bc']={'bc','e'}, ['bce']={'bc','e'}, ['before present']={'bp','e'}, ['bp']={'bp','e'}, ['ce']={'ad','e'}, ['ad']={'ad','e'}, ['ah']={'ah','e'}, -- conjunctions ['&']={'and','j'}, ['and']={'and','j'}, ['or']={'or','j'}, ['-']={'from-until','j'}, ['–']={'from-until','j'}, ['from-until']={'from-until','j'}, ['between']={'between','j'}, ['circa2']={'circa2','a'}, -- precision ['day']={'day','p'}, ['month']={'month','p'}, ['year']={'year','p'}, ['s']={'decade','p'}, ['decade']={'decade','p'}, ['century']={'century','p'}, ['millennium']={'millennium','p'}, ['million']={'million','p'}, ['billion']={'billion','p'}, -- calendar ['julian']={'julian','c'}, ['islamic']={'islamic','c'}, ['hijri']={'islamic','c'}, -- "Turn of the ..." options are a combos which will be handled in similar way as calendars ['turn of the century']={'turn of the century','c'}, ['century turn']={'turn of the century','c'}, ['turn of the decade']={'turn of the decade','c'}, ['decade turn']={'turn of the decade','c'}, ['turn of the year']={'turn of the year','c'}, ['year turn']={'turn of the year','c'}, -- other dates which will be handled in similar way as calendars ['?']={'unknown','c'}, ['unknown']={'unknown','c'}, ['unknown year']={'year unknown','c'}, ['year unknown']={'year unknown','c'}, } return p g9dzfs88xlwnkv4sku4bi0qkysnjj4o 803607 803606 2019-02-24T14:30:15Z en>RexxS 0 call Module:Ordinal-cd, which is a copy of the version used on Commons 803607 Scribunto text/plain --[[ __ __ _ _ ___ _ ___ __ _ _ _ | \/ | ___ __| |_ _| | ___ _|_ _/ |( _ ) _ __ / /__ ___ _ __ ___ _ __ | | _____ __ __| | __ _| |_ ___ | |\/| |/ _ \ / _` | | | | |/ _ (_)| || |/ _ \| '_ \ / / __/ _ \| '_ ` _ \| '_ \| |/ _ \ \/ / / _` |/ _` | __/ _ \ | | | | (_) | (_| | |_| | | __/_ | || | (_) | | | |/ / (_| (_) | | | | | | |_) | | __/> < | (_| | (_| | || __/ |_| |_|\___/ \__,_|\__,_|_|\___(_)___|_|\___/|_| |_/_/ \___\___/|_| |_| |_| .__/|_|\___/_/\_\ \__,_|\__,_|\__\___| |_| This code was copied from Commons please request changes there. Maintainers: *Jarekt ]] local Ordinal = require('Module:Ordinal-cd')._Ordinal local Roman = require('Module:Roman')._Numeral local linguistic = require('Module:Linguistic') -- ========================================================================== -- === Local Helper functions ============================================== -- ========================================================================== -- form of French "of" based on "precision", which is wikibase compatible date precision format: -- 6=millennium, 7=century, 8=decade, 9=year, 10=month, 11=day -- 1 2 3 4 5 mill cent deca year month day local frOf = {'du','du','du','du','du', 'du', 'du', 'de', 'de', 'de', 'du'} local function decYear(state,lang,case) --Declension of word "year" in several languages if state.precision == 9 then yearCase = { fi={xxx='vuosien', gen='vuoden', ill='vuoteen', ptv='vuotta', ela='vuodesta', ine='vuonna'}, sl={insP='letoma', ins='letom', gen='leta', loc='letu'}, hsb={insP='lětami', ins='lětom', gen='lěta', loc='lěće'} } return yearCase[lang][case] .. ' ' else return '' end end local function decCentury(lang,case) --Declension of word "century" in several languages centuryCase = { pl={nom='wiek', gen='wieku', ins='wiekiem', loc='wieku', default = 'wiek'}, hsb={nom='lětstotk', gen='lětstotka', ins='lětstotkom', loc='lětstotku', default = 'lětstotk'} } return centuryCase[lang][case] or centuryCase[lang]['default'] end local function decMillennium(lang,case) --Declension of word "century" in several languages millenniumCase = { pl={nom='tysiąclecie', gen='tysiąclecia', ins='tysiącleciem', loc='tysiącleciu', default = 'tysiąclecie'}, hsb={nom='lěttysac', gen='lěttysaca', ins='lěttysacom', loc='lěttysacu', default = 'lěttysac'} } return millenniumCase[lang][case] or millenniumCase[lang]['default'] end -- ========================================================================== -- === Output data structure =============================================== -- ========================================================================== local p = {} --[[ Multiple formats are possible: 1) string with key words "$date1" and "$date2" which will be replaced later by the date strings 2) function which can be used to create output string * Function INPUTS: - date1 and date2 string with date strings. "$date1" and "$date2" can also be used which will be replaced latter - state is a table with full description of the phrase we are trying to construct. It includes state.precision1 (and 2) which is wikibase compatible date precision format: 6=millennium, 7=century, 8=decade, 9=year, 10=month, 11=day 3) table with following fields: * table[1] is either string or a function * table.case is a 2 element table with Grammatical cases used by given language to alter month name or other adjective related to date # 1 and 2 ]] p.Translations = { -- ========================================== -- === Two date phrases ===================== -- ========================================== ['from-until'] = { nDates = 2, ar='من $date1 إلى $date2', ['be-tarask']={'з $date1 да $date2', case={'gen','gen'}}, bn='$date1 থেকে $date2 পর্যন্ত', ca='$date1 fins $date2', cs='$date1 až $date2', da='fra $date1 til $date2', de='$date1 bis $date2', el='από $date1 έως $date2', en='from $date1 until $date2', ['en-gb']='from $date1 until $date2', ['en-ca']='from $date1 until $date2', eo='de $date1 al $date2', es='de $date1 a $date2', et='$date1 kuni $date2', fa='از $date1 تا $date2', fi='$date1–$date2', -- välisenä aikana fr=function(date1, date2, state) -- could actually be better should be "d'octobre à mars 2013' not "de octore 2013 à mars 2013" if state.precision > 10 then -- precision > month return 'du $date1 au $date2' else -- "DE septebmbre" / "D'octobre" return linguistic.of(date1, 'fr') .. " à $date2" end end, gl='de $date1 a $date2', --Template:From until used 'desde $date1 ata $date2' he='מ- $date1 עד $date2', hr='od $date1 do $date2', hsb={'wot $date1 do $date2', case={'gen','gen'}}, hu='$date1 – $date2', it='dal $date1 al $date2', ja='$date1から$date2まで', mk='од $date1 до $date2', ml='$date1 മുതൽ $date2 വരെ', nb='fra $date1 til $date2', nds='$date1 bet $date2', nn='fra $date1 til $date2', nl='$date1 tot $date2', no='fra $date1 til $date2', pl={'od $date1 do $date2', case={'gen','gen'}}, ro='din $date1 până în $date2', pt='de $date1 a $date2', qu="$date1 p'unchawmanta $date2 p\'unchawkama", ru={'с $date1 по $date2', case={'gen','gen'}}, sk={'od $date1 do $date2', case={'gen','gen'}}, sl={'od $date1 do $date2', case={'gen','gen'}}, sv='från $date1 till $date2', uk={'з $date1 до $date2', case={'gen','gen'}}, ur='از $date1 تا $date2', vi='từ $date1 đến $date2', ['zh-hans']='从$date1到$date2', ['zh-hant']='從$date1到$date2', default='<span style="color:red">$date1 - $date2</span>', }, ['between']={ nDates = 2, ar='بين $date1 و $date2', ['be-tarask']={'між $date1 і $date2', case={'ins','ins'}}, bn='$date1 এবং $date2-এর মাঝামাঝি', ca='entre $date1 i $date2', cs={'mezi $date1 a $date2', case={'ins','ins'}}, da='mellem $date1 og $date2', de='zwischen $date1 und $date2', el='μεταξύ $date1 και $date2', en='between $date1 and $date2', ['en-gb']='between $date1 and $date2', ['en-ca']='between $date1 and $date2', eo='inter $date1 kaj $date2', es='entre $date1 y $date2', et='$date1 ja $date2 vahel', fa='بین $date1 و $date2', fi={function(date1, date2, state) return decYear(state,'fi','xxx') .. '$date1 ja $date2 välisenä aikana' end, case={'gen','gen'}}, fr='entre $date1 et $date2', gl='entre $date1 e $date2', he='בין $date1 ו- $date2', hr='između $date1 i $date2', hsb={'mjez $date1 a $date2', case={'ins','ins'}}, hu='$date1 és $date2 között', hi='$date1 और $date2 के बीच', it='tra il $date1 e il $date2', ja='$date1と$date2の間', mk='помеѓу $date1 и $date2', ml='$date1, $date2 എന്നിവയ്ക്കിടയ്ക്ക്', nb='mellom $date1 og $date2', nds='twischen $date1 un $date2', nn='mellom $date1 og $date2', nl='tussen $date1 en $date2', no='mellom $date1 og $date2', pl={'między $date1 a $date2', case={'ins','ins'}}, ro='între $date1 și $date2', pt='entre $date1 e $date2', qu="$date1 p'unchawpa $date2 p'unchawpapas chawpin", ru={'между $date1 и $date2', case={'ins','ins'}}, sk={'medzi $date1 a $date2', case={'ins','ins'}}, sl={function(date1, date2, state) return 'med ' .. decYear(state,'sl','insP') .. '$date1 in $date2' end, case={'ins','ins'}}, sv='mellan $date1 och $date2', uk={'між $date1 та $date2', case={'ins','ins'}}, ur='بین $date1 و $date2', vec='tra el $date1 e el $date2', vi='giữa $date1 và $date2', ['zh-hans']='在$date1到$date2之间', ['zh-hant']='在$date1到$date2之間', default='<span style="color:red">$date1 - $date2</span>', }, ['or'] = { nDates = 2, ar='$date1 أو $date2', ['be-tarask']='$date1 ці $date2', bn='$date1 অথবা $date2', ca='$date1 o $date2', cs='$date1 nebo $date2', da='$date1 eller $date2', de='$date1 oder $date2', el='$date1 ή $date2', en='$date1 or $date2', ['en-gb']='$date1 or $date2', ['en-ca']='$date1 or $date2', eo='$date1 aŭ $date2', es='$date1 o $date2', et='$date1 või $date2', fa='$date1 یا $date2', fi='$date1 tai $date2', fr='$date1 ou $date2', gl='$date1 ou $date2', he='$date1 או $date2', hr='$date1 ili $date2', hsb='$date1 abo $date2', hu='$date1 vagy $date2', it='$date1 o $date2', ja='$date1または$date2', mk='$date1 или $date2', ml='$date1 അഥവാ $date2', nb='$date1 eller $date2', nn='$date1 eller $date2', nds='$date1 oder $date2', nl='$date1 of $date2', no='$date1 eller $date2', pl='$date1 lub $date2', pt='$date1 ou $date2', qu='$date1 icha $date2', ro='$date1 sau $date2', ru='$date1 или $date2', sk='$date1 alebo $date2', sl='$date1 ali $date2', sv='$date1 eller $date2', uk='$date1 або $date2', ur='$date1 یا $date2', th='$date1 หรือ $date2', vi='$date1 hoặc $date2', ['zh-hans']='$date1或$date2', ['zh-hant']='$date1或$date2', default=function(date1, date2, state) return linguistic.conj({date1, date2}, state.lang, 'or') end, }, ['and'] = { nDates = 2, ar='$date1 و $date2', ['be-tarask']='$date1 і $date2', bn='$date1 এবং $date2', ca='$date1 i $date2', cs='$date1 a $date2', da='$date1 og $date2', de='$date1 und $date2', el='$date1 και $date2', en='$date1 and $date2', ['en-gb']='$date1 and $date2', ['en-ca']='$date1 and $date2', eo='$date1 kaj $date2', es='$date1 y $date2', et='$date1 ja $date2', fa='$date1 و $date2', fi='$date1 ja $date2', fr='$date1 et $date2', gl='$date1 e $date2', he='$date1 ו- $date2', hu='$date1 és $date2', hsb='$date1 a $date2', it='$date1 e $date2', ja='$date1および$date2', mk='$date1 и $date2', ml='$date1 ഒപ്പം $date2', nb='$date1 og $date2', nn='$date1 og $date2', nds='$date1 un $date2', nl='$date1 en $date2', no='$date1 og $date2', pl='$date1 i $date2', pt='$date1 e $date2', qu='$date1wan $date2pas', ro='$date1 și $date2', ru='$date1 и $date2', sk='$date1 a $date2', sl='$date1 in $date2', sv='$date1 och $date2', uk='$date1 та $date2', ur='$date1 و $date2', vi='$date1 và $date2', ['zh-hans']='$date1和$date2', ['zh-hant']='$date1和$date2', default=function(date1, date2, state) return linguistic.conj({date1, date2}, state.lang, 'and') end, }, -- ========================================== -- === Prepositions ========================= -- ========================================== ['by'] = { nDates = 1, ar='حوالي $date1', ['be-tarask']={'да $date1', case={'gen',''}}, bn='$date1 কর্তৃক', ca='vers $date1', cs={'kolem $date1', case={'gen',''}}, da='mod $date1', de='spätestens $date1', el='έως $date1', en='by $date1', ['en-gb']='by $date1', ['en-ca']='by $date1', eo='ne post $date1', es='para $date1', et='hiljemalt $date1', fa='توسط $date1', fi={function(date1, state) return decYear(state,'fi','ill') .. '$date1 mennessä' end, case={'ill',''}}, fr='$date1 (au plus tard)', gl='cara a $date1', he='עד $date1', hr='do $date1', hsb={'do $date1', case={'gen',''}}, it='entro il $date1', ja='$date1まで', mk='кон $date1', ml='$date1-ൽ', nb='innen $date1', nn='innan $date1', nds='$date1 oder vördem al', nl='tegen $date1', no='innen $date1', pl={'przed $date1', case={'ins',''}}, pt='por $date1', qu="$date1 p'unchawkama", ro='de $date1', ru={'к $date1', case={'dat',''}}, sk={'okolo $date1', case={'gen',''}}, sl={function(date1, state) return 'najpozneje ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='senast $date1', uk={'до $date1', case={'dat',''}}, ur='توسط $date1', vi='vào $date1', -- need to insert "đã" somewhere later in the sentence --> ['zh-hans']='$date1前后', ['zh-hant']='$date1前后', default='<span style="color:red">by $date1</span>', }, ['before'] = { nDates = 1, ar='قبل $date1', ['be-tarask']={'перад $date1', case={'ins',''}}, bn='$date1-এর আগে', bg='Преди $date1', ca='abans $date1', cs={'před $date1', case={'ins',''}}, da='før $date1', de='vor $date1', el='πριν από $date1', en='before $date1', ['en-gb']='before $date1', ['en-ca']='before $date1', eo='antaŭ $date1', es='antes de $date1', et='enne $date1', fa='پیش از $date1', fi={function(date1, state) return 'ennen ' .. decYear(state,'fi','ptv') .. '$date1' end, case={'ptv',''}}, fr='avant $date1', gl='antes do $date1', he='לפני $date1', hr='prije $date1', hsb={'do $date1', case={'gen',''}}, hu='$date1 előtt', it='prima del $date1', ja='$date1より前', mk='пред $date1', ml='$date1-നു മുമ്പ്', nb='før $date1', nn='før $date1', nds='vör $date1', nl='voor $date1', no='før $date1', pl={'przed $date1', case={'ins',''}}, pt='antes de $date1', qu="$date1 p'unchaw ñawpan", ro='înainte de $date1', ru={'ранее $date1', case={'gen',''}}, sk={'pred $date1', case={'ins',''}}, sl={function(date1, state) return 'pred ' .. decYear(state,'sl','ins') .. ' $date1' end, case={'ins',''}}, sv='före $date1', uk={'до $date1', case={'gen',''}}, ur='قبل $date1', vi='trước $date1', ['zh-hans']='在$date1之前', ['zh-hant']='在$date1之前', default='<span style="color:red">before $date1</span>', }, ['from'] = { nDates = 1, ar='من $date1', ['be-tarask']={'з $date1', case={'gen',''}}, bn='$date1 থেকে', cs={'od $date1', case={'gen',''}}, de='Seit $date1', el='από $date1', en='from $date1', ['en-gb']='from $date1', ['en-ca']='from $date1', eo='ekde $date1', es='desde $date1', et='alates $date1', fa='از $date1', fi={function(date1, state) return decYear(state,'fi','ela') .. '$date1 alkaen' end, case={'ela',''}}, fr= function(date1, state) return 'à partir '.. frOf[state.precision] ..' $date1' end, gl='desde o $date1', he='מ- $date1', hr='od $date1', hsb={'wot $date1', case={'gen',''}}, it='dal $date1', ja='$date1から', nds='vonaf $date1', mk='од $date1', nb='fra $date1', nn='frå $date1', ml='$date1 മുതൽ', nl='vanaf $date1', no='fra $date1', pl={'od $date1', case={'gen',''}}, qu="$date1 p'unchawmanta", ro='din $date1', ru={'С $date1', case={'gen',''}}, sk={'od $date1', case={'gen',''}}, sl={function(date1, state) return 'od ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='från $date1', uk={'з $date1', case={'gen',''}}, ur='از $date1', vi='từ $date1', ['zh-hans']='自$date1', ['zh-hant']='自$date1', default='<span style="color:red">from $date1</span>', }, ['until'] = { nDates = 1, ['be-tarask']={'усутыч да $date1', case={'gen',''}}, bn='$date1 পর্যন্ত', cs={'do $date1', case={'gen', ''}}, de='bis $date1', en='until $date1', ['en-gb']='until $date1', ['en-ca']='until $date1', eo='ĝis $date1', es='hasta $date1', fr=function(date1, state) if state.precision > 10 then -- precision > month return "jusqu'au $date1" else return "jusqu'à $date1" end end, hr='do $date1', hsb={'do $date1', case={'gen',''}}, it='al $date1', nb='til $date1', nl='tot $date1', nn='til $date1', ml='$date1 വരെ', pl={'do $date1', case={'gen',''}}, ru={'вплоть до $date1', case={'gen',''}}, sk={'do $date1', case={'gen', ''}}, uk={'аж до $date1', case={'gen',''}}, ur='تک $date1', ['zh-hans']='直到$date1', ['zh-hant']='直到$date1', default='<span style="color:red">until $date1</span>', }, ['after'] = { nDates = 1, ar='بعد $date1', ['be-tarask']={'пасьля $date1', case={'gen',''}}, bn='$date1-এর পরে', ca='després de $date1', cs={'po $date1', case={'loc',''}}, de='nach $date1', da='efter $date1', el='μετά από $date1', en='after $date1', ['en-gb']='after $date1', ['en-ca']='after $date1', eo='post $date1', es='después de $date1', et='pärast $date1', fa='پس از $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 jälkeen' end, case={'gen',''}}, fr='après $date1', gl='despois do $date1', he='לאחר $date1', hr='nakon $date1', hu='$date1 után', hsb={'po $date1', case={'loc',''}}, it='dopo il $date1', ja='$date1より後', mk='по $date1', ml='$date1-നു ശേഷം', nb='etter $date1', nn='etter $date1', nds='na $date1', nl='na $date1', no='etter $date1', pl={'po $date1', case={'loc',''}}, pt='após $date1', qu="$date1 p'unchaw qhipan", ro='după $date1', ru={'после $date1', case={'gen',''}}, sk={'po $date1', case={'loc',''}}, sl={function(date1, state) return 'po ' .. decYear(state,'sl','loc') .. ' $date1' end, case={'loc',''}}, sv='efter $date1', uk={'після $date1', case={'gen',''}}, ur='بعد $date1', vi='sau $date1', ['zh-hans']='在$date1之后', ['zh-hant']='在$date1之後', default='<span style="color:red">after $date1</span>', }, -- ========================================== -- === Adjectives =========================== -- ========================================== ['early'] = { nDates = 1, ar='بداية $date1', ['be-tarask']={'пачатак $date1', case={'gen',''}}, bn='$date1-এর শুরুতে', ca='al principi de $date1', cs={'začátek $date1', case={'gen',''}}, da='tidlig $date1', de='Anfang $date1', el='αρχές του $date1', en='early $date1', ['en-gb']='early $date1', ['en-ca']='early $date1', eo='komence de $date1', es='a principios de $date1', et='$date1 algus', fa='اوایل $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 alku' end, case={'gen',''}}, fr='début $date1', gl='a principios de $date1', he='תחילת $date1', hr='početak $date1', hsb={'spočatk $date1', case={'gen',''}}, hu='$date1 elején', it='inizio del $date1', ja='$date1初頭', mk='почеток на $date1', ml='$date1 ആദ്യപാദം', nb='tidlig $date1', nn='tidleg $date1', nds='Anfang $date1', nl='begin $date1', no='tidlig $date1', pl={'początek $date1', case={'gen',''}}, pt='a princípios de $date1', qu="$date1 ñawpaq p'unchawkunapi", ro='începând cu $date1', ru={'начало $date1', case={'gen',''}}, sk={'začiatok $date1', case={'gen',''}}, sl=function(date1, state)-- could actually be better should be "d'octobre à mars 2013' not "de octore 2013 à mars 2013" if state.precision == 9 then -- precision > month return 'zgodnje leto $date1' else -- "DE septebmbre" / "D'octobre" return 'zgodnji $date1' end end, sv='början av $date1', uk={'початок $date1', case={'gen',''}}, ur='ابتدا $date1', vi='đầu năm $date1', ['zh-hans']='$date1早期', ['zh-hant']='$date1早期', default='<span style="color:red">early $date1</span>', }, ['mid'] = { nDates = 1, ar='منتصف $date1', ['be-tarask']={'сярэдзіна $date1', case={'gen',''}}, bn='$date1-এর মাঝামাঝি', ca='a mitjan $date1', cs={'polovina $date1', case={'gen',''}}, da='midten af $date1', de='Mitte $date1', el='μέσα του $date1', en='mid $date1', ['en-gb']='mid $date1', ['en-ca']='mid $date1', eo='meze de $date1', es='a mediados de $date1', et='$date1 keskel', fa='اواسط $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 puoliväli' end, case={'gen',''}}, fr='mi $date1', gl='a mediados de $date1', he='אמצע $date1', hr='sredina $date1', hsb={'srjedź $date1', case={'gen',''}}, hu='$date1 közepén', it='metà del $date1', ja='$date1半ば', mk='средина на $date1', ml='$date1 മദ്ധ്യം', nb='midten av $date1', nn='midten av $date1', nds='Midd $date1', nl='midden $date1', no='midten av $date1', pl={'środek $date1', case={'gen',''}}, pt='meados de $date1', qu="$date1 chawpin p'unchawkunapi", ro='mijlocul lui $date1', ru={'середина $date1', case={'gen',''}}, sk={'polovica $date1', case={'gen',''}}, sl={function(date1, state) return 'sredina ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='mitten av $date1', uk={'середина $date1', case={'gen',''}}, ur='وسط $date1', vi='giữa năm $date1', ['zh-hans']='$date1中期', ['zh-hant']='$date1中期', default='<span style="color:red">mid $date1</span>', }, ['late'] = { nDates = 1, ar='نهاية $date1', bn='$date1-এর শেষে', ['be-tarask']={'канец $date1', case={'gen',''}}, ca='al final de $date1', cs='konec $date1', da='sen $date1', de='Ende $date1', el='τέλη του $date1', en='late $date1', ['en-gb']='late $date1', ['en-ca']='late $date1', eo='fine de $date1', es='a fines de $date1', et='$date1 lõpp', fa='اواخر $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 loppu' end, case={'gen',''}}, fr='fin $date1', gl='a finais de $date1', he='סוף $date1', hr='kraj $date1', hsb={'kónc $date1', case={'gen',''}}, hu='$date1 végén', it='fine del $date1', ja='$date1末', mk='крајот на $date1', ml='$date1 അന്ത്യപാദം', nb='sent $date1', no='seint $date1', nds='Enn $date1', nl='eind $date1', no='sent $date1', pl={'koniec $date1', case={'gen',''}}, pt='ao final de $date1', qu="$date1 qhipaq p'unchawkunapi", ro='sfârșitul lui $date1', ru={'конец $date1', case={'gen',''}}, sk={'koniec $date1', case={'gen',''}}, sl={function(date1, state) return 'pozno ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='slutet av $date1', uk={'кінець $date1', case={'gen',''}}, ur='اخیر $date1', vi='cuối năm $date1', ['zh-hans']='$date1晚期', ['zh-hant']='$date1晚期', default='<span style="color:red">late $date1</span>', }, ['circa'] = { nDates = 1, ar='حوالي $date1', ['be-tarask']={'каля $date1', case={'gen',''}}, bn='প্রায় $date1', ca='cap a $date1', cs='cca $date1', da='ca. $date1', de='etwa $date1', el='περ. $date1', en='circa $date1', ['en-gb']='circa $date1', ['en-ca']='circa $date1', eo='ĉirkaŭ $date1', es='hacia $date1', -- suggestion use "hacia"/"hacia el" or leave the latin circa or ca. et='ca. $date1', fa='حوالی $date1', -- template:Circa used 'حدود $date1' fi={function(date1, state) return 'suunnilleen ' .. decYear(state,'fi','ine') .. '$date1' end, case={'ine',''}}, -- template:Circa used 'noin $date1' fr=function(date1, state) if state.precision >= 11 then -- precision >= day return 'vers le $date1' else return 'vers $date1' end end, gl='entre $date1', -- template:Circa used 'cara a $date1' he='בסביבות $date1', hr='oko $date1', hsb={'wokoło $date1', case={'gen',''}}, hu='$date1 körül', it='$date1 circa', ja='$date1頃', ko='$date1경', mk='~ $date1', -- template:Circa used 'o. $date1' ml= 'ഉദ്ദേശം $date1', nb='ca. $date1', nn='ca. $date1', nds='üm un bi $date1', nl='ca. $date1', no='ca. $date1', pl={'około $date1', case={'gen',''}}, pt='cerca de $date1', qu='$date1chá', ro='circa $date1', ru={'около $date1', case={'gen',''}}, sk='cca $date1', sl={function(date1, state) return 'ok. ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='c:a $date1', -- template:Circa used 'ca $date1' uk={'близько $date1', case={'gen', ''}}, ur='قریباً $date1', vi='vào khoảng $date1', ['zh-hans']='约$date1', ['zh-hant']='約$date1', default='<span style="color:red">circa $date1</span>', }, ['uncertain'] = { nDates = 1, ['zh-hans']='$date1(?)', ['zh-hant']='$date1(?)', default='$date1 (?)', }, probably = { -- see Q56644435 nDates = 1, ast = 'probablemente $date1', bn = 'সম্ভবত $date1', ca = 'probablement $date1', cs = 'pravděpodobně $date1', de = 'wahrscheinlich $date1', el = 'πιθανόν $date1', en = 'probably $date1', ['en-gb']='probably $date1', ['en-ca']='probably $date1', eo = 'probable $date1', es = 'probable $date1', fa = 'احتمالاً $date1', fr = 'probablement $date1', he = 'כפי הנראה $date1', hu = 'feltehetőleg $date1', it = 'probabile $date1', mk = 'веројатно $date1', ml = 'ഒരുപക്ഷേ $date1', nl = 'waarschijnlijk $date1', pl = 'prawdopodobnie $date1', pt = 'provavelmente $date1', ru = 'вероятно $date1', sv = 'antagligen $date1', uk = 'ймовірно $date1', default='<span style="color:red">probably $date1</span>', }, possibly = { -- see Q30230067 nDates = 1, ast = 'posiblemente $date1', bn = 'যথাসম্ভব $date1', cs = 'možná $date1', de = 'möglicherweise $date1', en = 'possibly $date1', ['en-gb']='possibly $date1', ['en-ca']='possibly $date1', eo = 'eble $date1', el = 'πιθανόν $date1', fa = 'احتمالاً $date1', fr = 'peut-être $date1', hu = 'talán $date1', mk = 'можеби $date1', ml = 'മിക്കവാറും $date1', nl = 'mogelijk $date1', pl = 'być może $date1', pt = 'possivelmente $date1', ro = 'poate $date1', ru = 'возможно $date1', sv = 'möjligen $date1', uk = 'можливо $date1', default='<span style="color:red">possibly $date1</span>', }, presumably = { -- see Q18122778 nDates = 1, ast = 'presumiblemente $date1', de = 'vermutlich $date1', en = 'presumably $date1', ['en-gb']='presumably $date1', ['en-ca']='presumably $date1', fr = 'présumé $date1', it = 'presumibilmente $date1', ml = 'ഊഹപ്രകാരം $date1', ru = 'предположительно $date1', pl = 'przypuszczalnie $date1', uk = 'здогадно $date1', default='<span style="color:red">presumably $date1</span>', }, ['year unknown']= { nDates = 0, ca='Any desconegut', ['be-tarask']='Год невядомы', bn='অজানা বছর', cs='neznámý rok', de='Jahr unbekannt', el='άγνωστο έτος', en='year unknown', ['en-gb']='year unknown', ['en-ca']='year unknown', eo='jaro nesciata', es='Año desconocido', fa='سال نامعلوم', fi='tuntematon vuosi', fr='Année inconnue', he='שנה לא ידועה', hsb='lěto njeznate', hr='nepoznata godina', it='anno sconosciuto', ja='年代不明', la='Sine anno', ml='വർഷം ലഭ്യമല്ല', mk='непозната година', nb='ukjent år', nn='ukjent år', nl='zonder jaar', no='ukjent år', pl='rok nieznany', qu='Mana riqsisqa wata', ru='Год неизвестен', sk='neznámy rok', sl='neznano leto', sv='Okänt år', uk='рік невідомий', ur='سال نامعلوم', vi='không rõ năm', ['zh-hans']='年份未知', ['zh-hant']='年份未知', default='<span style="color:red">year unknown</span>', }, -- ========================================== -- === Parts of a whole ===================== -- ========================================== ['firsthalf'] = { nDates = 1, ar='النصف الأول من $date1', ['be-tarask']={'першая палова $date1', case={'gen',''}}, bn='$date1-এর প্রথমার্ধ', ca='1a meitat de $date1', cs={'1. polovina $date1', case={'gen',''}}, da='1. halvdel af $date1', de='1. Hälfte $date1', el='πρώτο μισό του $date1', en='first half of $date1', ['en-gb']='first half of $date1', ['en-ca']='first half of $date1', eo='unua duono de $date1', es='primera mitad de $date1', et='$date1 esimene pool', fa='نیمهٔ نخست $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 alkupuolisko' end, case={'gen',''}}, fr='1<sup>re</sup> moitié de $date1', gl='primeira metade de $date1', he='במחצית הראשונה של $date1', hsb={'prěnja połojca $date1', case={'gen',''}}, hu='$date1 első felében', hr='prva polovina $date1', it='prima metà del $date1', ja='$date1上半期', mk='прва половина на $date1', ml='$date1 ആദ്യപകുതി', nb='første halvdel av $date1', nn='fyrste halvdelen av $date1', nds='1. Helft $date1', nl='eerste helft $date1', no='første halvdel av $date1', pl={'pierwsza połowa $date1', case={'gen',''}}, pt='primeira metade de $date1', qu='$date1 ñawpaq kuskanpi', ro='prima jumătate a lui $date1', ru={'первая половина $date1', case={'gen',''}}, sk={'1. polovica $date1', case={'gen',''}}, sl={function(date1, state) return 'prva polovica ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='första halvan av $date1', uk={'перша половина $date1', case={'gen',''}}, ur='نصف اول $date1', vi='nửa đầu năm $date1', ['zh-hans']='$date1上半期', ['zh-hant']='$date1上半期', default='<span style="color:red">first half of $date1</span>', }, ['secondhalf'] = { nDates = 1, ar='النصف الثاني من $date1', ['be-tarask']={'другая палова $date1', case={'gen',''}}, bn='$date1-এর দ্বিতীয়ার্ধ', ca='2a meitat de $date1', cs={'2. polovina $date1', case={'gen',''}}, da='2. halvdel af $date1', de='2. Hälfte $date1', el='δεύτερο μισό του $date1', en='second half of $date1', ['en-gb']='second half of $date1', ['en-ca']='second half of $date1', eo='dua duono de $date1', es='segunda mitad de $date1', et='$date1 teine pool', fa='نیمهٔ دوم $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 loppupuolisko' end, case={'gen',''}}, fr='2<sup>e</sup> moitié de $date1', gl='segunda metade de $date1', he='במחצית השנייה של $date1', hsb={'druha połojca $date1', case={'gen',''}}, hr='druga polovina $date1', hu='$date1 második felében', it='seconda metà del $date1', ja='$date1下半期', mk='втора половина на $date1', ml='$date1 രണ്ടാം പകുതി', nb='andre halvdel av $date1', nn='andre halvdelen av $date1', nds='2. Helft vun $date1', nl='tweede helft $date1', no='andre halvdel av $date1', pl={'druga połowa $date1', case={'gen',''}}, pt='segunda metade de $date1', qu='$date1 qhipaq kuskanpi', ro='a doua jumătate a lui $date1', ru={'вторая половина $date1', case={'gen',''}}, sk={'2. polovica $date1', case={'gen',''}}, sl={'druga polovica {{IfNum | $date0 | leta}} $date1', case={'gen',''}}, sv='andra halvan av $date1', uk={'друга половина $date1', case={'gen',''}}, ur='نصف آخر $date1', vi='nửa cuối năm $date1', ['zh-hans']='$date1下半期', ['zh-hant']='$date1下半期', default='<span style="color:red">second half of $date1</span>', }, ['1quarter']= { nDates = 1, bn='$date1-এর প্রথম ত্রৈমাসিক', ['be-tarask']={'першая чвэрць $date1', case={'gen',''}}, cs={'1. čtvrtina $date1', case={'gen',''}}, de='erstes Viertel $date1', en='first quarter of $date1', ['en-gb']='first quarter of $date1', ['en-ca']='first quarter of $date1', eo='unua kvarono de $date1', es='primer cuarto del $date1', fa='ربع نخست $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 ensimmäinen neljännes' end, case={'gen',''}}, fr= function(date1, state) return 'premier quart '.. frOf[state.precision] ..' $date1' end, he='רבעון ראשון של $date1', hsb={'prěnja štwórćina $date1', case={'gen',''}}, hr='prva četvrtina $date1', it='primo quarto del $date1', ja='$date1第1四半期', ml='$date1 ഒന്നാം ത്രൈമാസം', nb='første kvartal av $date1', nl='eerste kwart $date1', nn='fyrste kvartalet av $date1', no='første kvartal av $date1', pl={'pierwsza ćwiartka $date1', case={'gen',''}}, qu='$date1 ñawpaq ñiqin tawa phatmanpi', ru={'первая четверть $date1', case={'gen',''}}, sk={'1. štvrtina $date1', case={'gen',''}}, sl={'prva četrtina {{IfNum | $date0 | leta}} $date1', case={'gen',''}}, sl={function(date1, state) return 'prva četrtina ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='första kvartalet av $date1', uk={'1-ша чверть $date1', case={'gen',''}}, ur='پہلی سہ ماہی $date1', vi='quý 1 năm $date1', ['zh-hans']='$date1第一季度', ['zh-hant']='$date1第一季度', default='<span style="color:red">first quarter of $date1</span>', }, ['2quarter']= { nDates = 1, ['be-tarask']={'другая чвэрць $date1', case={'gen',''}}, bn='$date1-এর দ্বিতীয় ত্রৈমাসিক', cs={'2. čtvrtina $date1', case={'gen',''}}, de='zweites Viertel $date1', en='second quarter of $date1', ['en-gb']='second quarter of $date1', ['en-ca']='second quarter of $date1', eo='dua kvarono de $date1', es='segundo cuarto del $date1', fa='ربع دوم $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 toinen neljännes' end, case={'gen',''}}, fr= function(date1, state) return 'deuxième quart '.. frOf[state.precision] ..' $date1' end, he='רבעון שני של $date1', hsb={'druha štwórćina $date1', case={'gen',''}}, hr='druga četvrtina $date1', it='secondo quarto del $date1', ja='$date1第2四半期', ml='$date1 രണ്ടാം ത്രൈമാസം', nb='andre kvartal av $date1', nl='tweede kwart $date1', nn='andre kvartalet av $date1', no='andre kvartal av $date1', pl={'druga ćwiartka $date1', case={'gen',''}}, qu='$date1 iskay ñiqin tawa phatmanpi', ru={'вторая четверть $date1', case={'gen',''}}, sk={'2. štvrtina $date1', case={'gen',''}}, sl={function(date1, state) return 'druga četrtina ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='andra kvartalet av $date1', uk={'2-га чверть $date1', case={'gen',''}}, ur='دوسری سہ ماہی $date1', vi='quý 2 năm $date1', ['zh-hans']='$date1第二季度', ['zh-hant']='$date1第二季度', default='<span style="color:red">second quarter of $date1</span>', }, ['3quarter']= { nDates = 1, ['be-tarask']={'трэцяя чвэрць $date1', case={'gen',''}}, bn='$date1-এর তৃতীয় ত্রৈমাসিক', cs={'3. čtvrtina $date1', case={'gen',''}}, de='drittes Viertel $date1', en='third quarter of $date1', ['en-gb']='third quarter of $date1', ['en-ca']='third quarter of $date1', eo='tria kvarono de $date1', es='tercer cuarto del $date1', fa='ربع سوم $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 kolmas neljännes' end, case={'gen',''}}, fr= function(date1, state) return 'troisième quart '.. frOf[state.precision] ..' $date1' end, he='רבעון שלישי של $date1', hsb={'třeća štwórćina $date1', case={'gen',''}}, hr='treća četvrtina $date1', it='terzo quarto del $date1', ja='$date1第3四半期', ml='$date1 മൂന്നാം ത്രൈമാസം', nb='tredje kvartal av $date1', nl='derde kwart $date1', nn='tredje kvartalet av $date1', no='tredje kvartal av $date1', pl={'trzecia ćwiartka $date1', case={'gen',''}}, qu='$date1 kimsa ñiqin tawa phatmanpi', sk={'3. štvrtina $date1', case={'gen',''}}, sl={function(date1, state) return 'tretja četrtina ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, ru={'третья четверть $date1', case={'gen',''}}, sv='tredje kvartalet av $date1', uk={'3-тя чверть $date1', case={'gen',''}}, ur='تیسری سہ ماہی $date1', vi='quý 3 năm $date1', ['zh-hans']='$date1第三季度', ['zh-hant']='$date1第三季度', default='<span style="color:red">third quarter of $date1</span>', }, ['4quarter']= { nDates = 1, ['be-tarask']={'чацьвёртая чвэрць $date1', case={'gen',''}}, bn='$date1-এর চতুর্থ ত্রৈমাসিক', cs={'4. čtvrtina $date1', case={'gen',''}}, de='letztes Viertel $date1', en='fourth quarter of $date1', ['en-gb']='fourth quarter of $date1', ['en-ca']='fourth quarter of $date1', eo='kvara kvarono de $date1', es='último cuarto del $date1', fa='ربع چهارم $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 viimeinen neljännes' end, case={'gen',''}}, fr= function(date1, state) return 'quatrième quart '.. frOf[state.precision] ..' $date1' end, he='רבעון רביעי של $date1', hsb={'poslednja štwórćina $date1', case={'gen',''}}, hr='četvrta četvrtina $date1', it='quarto quarto del $date1', ja='$date1第4四半期', ml='$date1 നാലാം ത്രൈമാസം', nb='fjerde kvartal av $date1', nl='vierde kwart $date1', nn='tredje kvartalet av $date1', no='fjerde kvartal av $date1', pl={'czwarta ćwiartka $date1', case={'gen',''}}, qu='$date1 qhipaq ñiqin tawa phatmanpi', ru={'последняя четверть $date1', case={'gen',''}}, sk={'4. štvrtina $date1', case={'gen',''}}, sl={function(date1, state) return 'zadnja četrtina ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='fjärde kvartalet av $date1', uk={'4-та чверть $date1', case={'gen',''}}, ur='چوتھی سہ ماہی $date1', vi='quý 4 năm $date1', ['zh-hans']='$date1第四季度', ['zh-hant']='$date1第四季度', default='<span style="color:red">forth quarter of $date1</span>', }, -- ========================================== -- === Seasons ============================== -- ========================================== ['spring'] = { nDates = 1, ar='ربيع $date1', ['be-tarask']='вясна $date1', bn='বসন্তকাল $date1', ca='primavera $date1', cs={'jaro $date1', case={'gen', ''}}, da='Forår $date1', de='Frühling $date1', en='Spring $date1', ['en-gb']='Spring $date1', ['en-ca']='Spring $date1', eo='printempo $date1', es='primavera de $date1', et='$date1 kevad', fa='بهار $date1', fi='Kevät $date1', fr='printemps $date1', gl='primavera $date1', he='אביב $date1', hr='proljeće $date1', hsb='nalěćo $date1', hu='$date1 tavasza', it='primavera $date1', ja='{{#if:$date1|$date1年}}春', mk='пролет $date1', ml='$date1 വസന്തം', nb='våren $date1', nds='Vörjohr $date1', nn='våren $date1', nl='voorjaar $date1', no='våren $date1', pl='wiosna $date1', pt='primavera de $date1', ro='primăvara lui $date1', ru='весна $date1', sk={'jar $date1', case={'gen', ''}}, sl='pomlad $date1', sv='våren $date1', uk='весна $date1', ur='بہار $date1', vi='mùa đông năm $date1', ['zh-hans']='{{#if:$date1|$date1年}}春季', ['zh-hant']='{{#if:$date1|$date1年}}春季', default='<span style="color:red">Spring $date1</span>', }, ['summer'] = { nDates = 1, ar='صيف $date1', ['be-tarask']='лета $date1', bn='গ্রীষ্মকাল $date1', ca='estiu $date1', cs={'léto $date1', case={'gen', ''}}, da='Sommer $date1', de='Sommer $date1', en='Summer $date1', ['en-gb']='Summer $date1', ['en-ca']='Summer $date1', eo='somero $date1', es='verano de $date1', et='$date1 suvi', fa='تابستان $date1', fi='Kesä $date1', fr='été $date1', gl='verán $date1', he='קיץ $date1', hr='ljeto $date1', hsb='lěćo $date1', hu='$date1 nyara', it='estate $date1', ja='{{#if:$date1|$date1年}}夏', mk='лето $date1', ml='$date1 വേനൽ', nb='sommeren $date1', nds='Sommer $date1', nn='sumaren $date1', nl='zomer $date1', no='sommeren $date1', pl='lato $date1', pt='verão de $date1', ro='vara lui $date1', ru='лето $date1', sk={'leto $date1', case={'gen', ''}}, sl='poletje $date1', sv='sommaren $date1', uk='літо $date1', ur='گرما $date1', vi='mùa hè năm $date1', ['zh-hans']='{{#if:$date1|$date1年}}夏季', ['zh-hant']='{{#if:$date1|$date1年}}夏季', default='<span style="color:red">Summer $date1</span>', }, ['autumn'] = { nDates = 1, ar='خريف $date1', ['be-tarask']='восень $date1', bn='শরত্কাল $date1', ca='tardor $date1', cs={'podzim $date1', case={'gen', ''}}, da='Efterår $date1', de='Herbst $date1', en='Autumn $date1', ['en-gb']='Autumn $date1', ['en-ca']='Autumn $date1', eo='aŭtuno $date1', es='otoño de $date1', et='$date1 sügis', fa='پاییز $date1', fi='Syksy $date1', fr='automne $date1', gl='outono $date1', he='סתיו $date1', hr='jesen $date1', hsb='nazyma $date1', hu='$date1 ősze', it='autunno $date1', ja='{{#if:$date1|$date1年}}秋', mk='есен $date1', ml='$date1 ശരത്കാലം', nb='høsten $date1', nds='Harvst $date1', nn='hausten $date1', nl='herfst $date1', no='høsten $date1', pl='jesień $date1', pt='outono de $date1', ro='toamna lui $date1', ru='осень $date1', sk={'jeseň $date1', case={'gen', ''}}, sl='jesen $date1', sv='hösten $date1', uk='осінь $date1', ur='خزاں $date1', vi='mùa thu năm $date1', ['zh-hans']='{{#if:$date1|$date1年}}秋季', ['zh-hant']='{{#if:$date1|$date1年}}秋季', default='<span style="color:red">Autumn $date1</span>', }, ['winter'] = { nDates = 1, ar='شتاء $date1', ['be-tarask']='зіма $date1', bn='শীতকাল $date1', ca='hivern $date1', cs={'zima $date1', case={'gen', ''}}, da='Vinter $date1', de='Winter $date1', en='Winter $date1', ['en-gb']='Winter $date1', ['en-ca']='Winter $date1', eo='vintro $date1', es='invierno de $date1', et='$date1 talv', fa='زمستان $date1', fi='Talvi $date1', fr='hiver $date1', gl='inverno $date1', he='חורף $date1', hr='zima $date1', hsb='zyma $date1', hu='$date1 tele', it='inverno $date1', ja='{{#if:$date1|$date1年}}冬', mk='зима $date1', ml='$date1 ശൈത്യകാലം', nds='Winter $date1', nl='winter $date1', nb='vinteren $date1', nn='vinteren $date1', no='vinteren $date1', pl='zima $date1', pt='inverno de $date1', ro='iarna lui $date1', ru='зима $date1', sk={'zima $date1', case={'gen', ''}}, sl='zima $date1', sv='vintern $date1', uk='зима $date1', ur='سرما $date1', vi='mùa đông năm $date1', ['zh-hans']='{{#if:$date1|$date1年}}冬季', ['zh-hant']='{{#if:$date1|$date1年}}冬季', default='<span style="color:red">Winter $date1</span>', }, -- ========================================== -- === Decade/Century/Millennium ============ -- ========================================== ['decade'] = { nDates = 1, ar='أعوام $date1', ['be-tarask']='$date1-я гады', bn='$date1-এর দশক', ca='dècada del $date1', cs=function(date1, state) return ((date1 % 100 == 0) and 'první dekáda' or ((date1 % 100) .. '. léta')) .. ' ' .. (math.floor(date1 / 100) + 1) .. '. století' end, da="$date1'erne", de='$date1er-Jahre', el='δεκαετία του $date1', en='$date1s', ['en-gb']='$date1s', ['en-ca']='$date1s', eo='$date1-aj jaroj', es='años $date1', et='$date1ndad', fa='دههٔ $date1', fi='$date1-luvulla', fr='années $date1', gl='anos $date1', he='שנות ה-$date1', hr='$date1-ih', hsb='$date1te lěta', hu='$date1-s évek', it="{{#ifexpr: $date1 mod 100 = 0 | primo decennio del {{Roman|{{#expr: ($date1 / 100) + 1}}}} sec. | anni '{{#expr: $date1 mod 100 }} del {{Roman|{{#expr: floor($date1 / 100) + 1}}}} sec.}}", ja='$date1年代', mk='$date1-ти', ml='ദശകം $date1-ൽ', nb='$date1-årene', nn='$date1-åra', nds='Johren $date1', nl='jaren $date1', no='$date1-årene', pl=function(date1, state) local str = 'lata '..(date1 % 100)..'. ' if (date1 % 100 == 0) then str = 'pierwsza dekada ' elseif (date1 % 100 == 10) then str = 'druga dekada ' end return str .. Roman(math.floor(date1 / 100) + 1) .. ' wieku' end, pt='década de $date1', qu='$date1 watakuna', ro='anii $date1', ru='$date1-е годы', sk=function(date1, state) return ((date1 % 100 == 0) and 'prvé desaťročie' or ((date1 % 100) .. '. roky')) .. ' ' .. (math.floor(date1 / 100) + 1) .. '. storočia' end, sl='$date1-ta leta', sv='$date1-talet{{#switch:$date1|1300|1400|1500|1600|1700|1800|1900|2000|2100=&nbsp;(decennium)}}', uk='$date1-ті роки', ur='دہائی $date1', vi='thập niên $date1', ['zh-hans']='$date1年代', ['zh-hant']='$date1年代', default='<span style="color:red">$date1s</span>', }, ['century'] = { nDates = 1, ar='القرن $date1', ['be-tarask']=function(date1, state) return Ordinal(date1, 'be-tarask') .. ' стагодзьдзе' end, bn=function(date1, state) return Ordinal(date1, 'bn') .. ' শতাব্দী' end, ca='segle {{small-caps|{{lc:{{Roman year|$date1}}}}}}', cs='$date1. století', da='$date1. århundrede', de='$date1. Jahrhundert', el='$date1ος αιώνας', en=function(date1, state) return Ordinal(date1, 'en') .. ' century' end, ['en-gb']=function(date1, state) return Ordinal(date1, 'en-gb') .. ' century' end, ['en-ca']=function(date1, state) return Ordinal(date1, 'en-ca') .. ' century' end, eo='$date1-a jarcento', es=function(date1, state) return 'siglo ' .. Roman(date1) end, et='$date1. sajand', fa='سدهٔ $date1', fi='{{#expr: ($date1 - 1) * 100}}-luvulla', fr=function(date1, state) return Ordinal(date1, 'fr', 'roman', 'm') .. ' siècle' end, gl=function(date1, state) return 'século ' .. Roman(date1) end, he='המאה ה-$date1', hr='$date1. stoljeće', hsb='$date1. lětstotk', hu='$date1. évszázad', it=function(date1, state) return Roman(date1) .. ' sec.' end, ja='$date1世紀', la='saeculum $date1', mk=function(date1, state) return Roman(date1) .. ' век' end, ml='$date1-ആം നൂറ്റാണ്ട്', nb=function(date1, state) return Ordinal(date1, 'nb') .. ' århundre' end, nn=function(date1, state) return 'det ' .. Ordinal(date1, 'no') .. ' århundre' end, nds='$date1. Johrhunnert', nl='$date1e eeuw', no=function(date1, state) return Ordinal(date1, 'no') .. ' århundre' end, pl=function(date1, state) return Roman(date1) .. ' ' .. decCentury('pl',state.case) end, pt=function(date1, state) return 'século ' .. Roman(date1) end, qu='$date1 ñiqin pachakwata', ro='secolul al $date1-lea', ru=function(date1, state) return Roman(date1) .. ' век' end, sk='$date1. storočie', sl=function(date1, state) return Ordinal(date1, 'sl') .. '. stoletje' end, sv='{{#expr:$date1 - 1}}00-talet', tt=function(date1, state) return Roman(date1) .. ' гасыр' end, uk=function(date1, state) return Roman(date1) .. ' ст.' end, ur='صدی $date1', vi='thế kỷ $date1', ['zh-hans']='$date1世纪', ['zh-hant']='$date1世紀', default='<span style="color:red">$date1 century</span>', }, ['millennium'] = { nDates = 1, ['be-tarask']=function(date1, state) return Ordinal(date1, 'be-tarask') .. ' тысячагодзьдзе' end, bn=function(date1, state) return Ordinal(date1, 'bn') .. ' সহস্রাব্দ' end, cs='$date1. tisíciletí', de='$date1. Jahrtausend', en=function(date1, state) return Ordinal(date1, 'en') .. ' millennium' end, ['en-gb']=function(date1, state) return Ordinal(date1, 'en-gb') .. ' millennium' end, ['en-ca']=function(date1, state) return Ordinal(date1, 'en-ca') .. ' millennium' end, eo='$date1-a jarmilo', es=function(date1, state) return Roman(date1) .. ' milenio' end, fa=function(date1, state) return 'هزارهٔ ' .. Ordinal(date1, 'fa') end, fr=function(date1, state) return Ordinal(date1, 'fr', 'roman', 'm') .. ' millénaire' end, hr='$date1. tisućljeće', hsb='$date1. lěttysac', it=function(date1, state) return Roman(date1) .. ' millennio' end, ja='$date1千年紀', la='millennium $date1', ml='$date1-മത്തെ സഹസ്രാബ്ദം', nb=function(date1, state) return Ordinal(date1, 'nb') .. ' årtusen' end, nn=function(date1, state) return Ordinal(date1, 'no') .. ' tusenår' end, nl='$date1e millennium', no=function(date1, state) return Ordinal(date1, 'no') .. ' årtusen' end, pl=function(date1, state) return Roman(date1) .. ' ' .. decMillennium('pl',state.case) end, pt=function(date1, state) return Roman(date1) .. ' milénio' end, qu='$date1 ñiqin waranqawata', ru=function(date1, state) return Roman(date1) .. ' тысячелетие' end, sk='$date1. tisícročie', sl=function(date1, state) return Ordinal(date1, 'sl') .. '. tisočletje' end, sv=function(date1, state) return Ordinal(date1, 'sv') .. ' årtusendet' end, uk=function(date1, state) return Roman(date1) .. ' тис.' end, ur=function(date1, state) return 'ہزارہ ' .. Ordinal(date1, 'ur') end, vi='thiên niên kỷ $date1', ['zh-hans']=function(date1, state) return Ordinal(date1, 'zh-hans') .. '千年' end, ['zh-hant']=function(date1, state) return Ordinal(date1, 'zh-hant') .. '千年' end, --default='{{Please translate|$date1 millennium|Module:I18n/complex date}}', default='<span style="color:red">$date1 millennium</span>', }, ['million'] = { de = '$date1 Millionen Jahre', en = '$date1 million years', ['en-gb'] = '$date1 million years', ['en-ca'] = '$date1 million years', eo = '$date1 milionoj da jaroj', es = '$date1 millones de años', nl = '$date1e miljoen jaar', fr = '$date1 millions d’années', it = '$date1 milioni di anni', ml = function(date1, state) if date1 >= 10 then return (date1 / 10) .. ' കോടി വർഷങ്ങൾ' -- 100 lakh = 1 crore else return (date1 * 10) .. ' ലക്ഷം വർഷങ്ങൾ' end end, nb = '$date1 millioner år', nl = '$date1 miljoen jaar', nn = '$date1 millionar år', pl = '$date1 milionów lat', pt = '$date1 milhões de anos', ru = '$date1 миллиона лет', uk = '$date1 мільйони років', ur = '$date1 ملین سال', ['zh-hans'] = '$date1百万年', ['zh-hant'] = '$date1百万年', default='<span style="color:red">$date1 million years</span>', nDates = 1, }, ['billion'] = { de = '$date1 Milliarden Jahre', en = '$date1 billion years', ['en-gb'] = '$date1 billion years', ['en-ca'] = '$date1 billion years', es = '$date1 mil millones de años', nl = '$date1e miljard jaar', fr = '$date1 milliard d’années', it = '$date1 miliardi di anni', ml = function(date1, state) return (date1 * 10) .. ' കോടി വർഷങ്ങൾ' end, nb = '$date1 milliarder år', nl = '$date1 miljard jaar', nn = '$date1 milliardar år', pl = '$date1 miliardów lat', pt = '$date1 mil milhões de anos', ru = '$date1 миллиардов лет', uk = '$date1 мільярдів років', ur = '$date1 بلین سال', ['zh-hans'] = '$date1十亿年', ['zh-hant'] = '$date1十億年', default='<span style="color:red$date1 billion years</span>', nDates = 1, }, -- ========================================== -- === Complex phrases ====================== -- ========================================== -- Phrases which for some languages can be build out of simpler phrases -- Their "default" state, used when no translation in the requested language was found, returns a "state" like -- table which is used to call the function again and built the phrase out of simple phrases -- $date1 - original date - 1 -- $date2 - original date -- if era is BC than $date1 and $date2 are swapped ['turn of the century'] = { nDates = 2, ['be-tarask']=function(date1, date2, state) return 'канец ' .. Roman(date1) ..' — пачатак ' .. Roman(date2) ..' стагодзьдзя' end, bn=function(date1, date2, state) return '$date1/' .. Ordinal(date2, 'bn') .. ' শতাব্দীর পালাবদলে' end, cs='přelom $date1. a $date2. století', de=function(date1, date2, state) return 'Jahrhundertwende $date1/' .. Ordinal(date2, 'de') end, en=function(date1, date2, state) return 'turn of the $date1/' .. Ordinal(date2, 'en') .. ' century' end, ['en-gb']=function(date1, date2, state) return 'turn of the $date1/' .. Ordinal(date2, 'en-gb') .. ' century' end, ['en-ca']=function(date1, date2, state) return 'turn of the $date1/' .. Ordinal(date2, 'en-ca') .. ' century' end, eo='ŝanĝo de la $date1-a al la $date2-a jarcentoj', es=function(date1, date2, state) return 'hacia finales del siglo ' .. Roman(date1) .. ' y principios del siglo ' .. Roman(date2) .. '' end, fi='{{#expr: $date1 * 100-100}}-luvun loppu ja {{#expr: $date2 * 100-100}}-luvun alku', fr=function(date1, date2, state) return 'fin du ' .. Ordinal(date1, 'fr', 'roman', 'm') .. ' siècle / début du ' .. Ordinal(date2, 'fr', 'roman', 'm') end, hr='kraj $date1. i početak $date2. stoljeća', hsb=function(date1, date2, state) return 'přechod ' .. Roman(date1) ..' do ' .. Roman(date2) ..' lětstotka' end, it=function(date1, date2, state) return 'fine del ' .. Roman(date1) .. ' sec. e inizio del ' .. Roman(date2) .. ' sec.' end, ja='$date1世紀から$date2世紀への変わり目', ml='$date1 നൂറ്റാണ്ട് അവസാനവും $date2 നൂറ്റാണ്ട് ആദ്യവും', nb=function(date1, date2, state) return 'slutten av det ' .. Ordinal(date1, 'nb') .. ' århundre/begynnelsen av det ' .. Ordinal(date2, 'nb') .. ' århundre' end, nl=function(date1, date2, state) return 'eind $date1e/begin $date2e eeuw' end, nn=function(date1, date2, state) return 'slutten av det ' .. Ordinal(date1, 'nn') .. ' hundreåret/byrjinga av det ' .. Ordinal(date2, 'nn') .. ' hundreåret' end, pl=function(date1, date2, state) return 'przełom ' .. Roman(date1) ..' i ' .. Roman(date2) ..' wieku' end, qu="$date1 ñiqin waranqawatamanta $date2 ñiqin waranqawataman t'ikrasqanpi", ru=function(date1, date2, state) return 'конец ' .. Roman(date1) ..' — начало ' .. Roman(date2) ..' века' end, sk='prelom $date1. a $date2. storočia', sl=function(date1, date2, state) return 'pozno ' .. Ordinal(date1, 'sl') .. '. in zgodnje ' .. Ordinal(date2, 'sl') .. '. stoletje' end, sv=function(date1, date2, state) return '$date1/' .. Ordinal(date2, 'sv') .. ' årtusendets sekelskifte' end, uk=function(date1, date2, state) return 'кін. ' .. Roman(date1) ..' ст. — поч. ' .. Roman(date2) ..' ст.' end, vi='bước sang thế kỷ $date2', ['zh-hans']=function(date1, date2, state) return '$date1世纪末' .. Ordinal(date2, 'zh-hans') .. '世纪初' end, ['zh-hant']=function(date1, date2, state) return '$date1世紀末' .. Ordinal(date2, 'zh-hant') .. '世紀初' end, default=function(date1, date2, state) return {conj='and', adj1 = 'late', unit1='century', era1=state.era1, adj2 = 'early', unit2='century', era2=state.era2} end, }, -- $date1 - original date - 10 -- $date2 - original date -- if era is BC than $date1 and $date2 are swapped ['turn of the decade']={ nDates = 2, ['be-tarask']='канец $date1-х — пачатак $date2-х гг.', bn='$date1-এর দশকের শেষে এবং $date2-এর দশকের শুরুতে', cs=function(date1, date2, state) local decade1 = math.floor((date1 % 100) / 10) local decade2 = math.floor((date2 % 100) / 10) local century1 = math.floor( date1 / 100) + 1 local century2 = math.floor( date2 / 100) + 1 if decade1 == 0 and date1 < date2 then return "přelom 1. a 2. dekády " .. century1 .. ". století" elseif decade2 == 0 and date2 < date1 then return "přelom 9. a 10. dekády " .. century1 .. ". století" elseif (decade1 == 9 and date1 < date2) or (decade2 == 9 and date2 < date1) then return "přelom " .. century1 .. ". a " .. century2 .. ". století" else return "přelom " .. decade1 .. "0. a " .. decade2 .. "0. let " .. century1 .. ". století" end end, de='späte $date1er und frühe $date2er', en='late $date1s and early $date2s', ['en-gb']='late $date1s and early $date2s', ['en-ca']='late $date1s and early $date2s', eo='ŝanĝo de la $date1-a al la $date2-a jardekoj', es='finales de los años $date1 y principios de los años $date2', fa='اواخر دههٔ $date1 و اوایل دههٔ $date2', fi='$date1-luvun loppu ja $date2-luvun alku', fr='fin des années $date1 / début des années $date2', hr='kraj $date1-ih i početak $date2-ih', hsb='pózdnje $date1te a zažne $date2te lěta', it='fine degli anni $date1 / inizio degli anni $date2', ja='$date1年代末から$date2年代初頭', ml='$date1കൾ അവസാനവും $date2കൾ ആദ്യവും', nb = 'sent på $date1-tallet og tidlig på $date2-tallet', nl = 'eind $date1e/begin $date2e decennium', nn = 'seint på $date1-talet og tidleg på $date2-talet', pl=function(date1, date2, state) local decade1 = math.floor(date1 % 100) local decade2 = math.floor(date2 % 100) local century1 = Roman(math.floor( date1 / 100) + 1) local century2 = Roman(math.floor( date2 / 100) + 1) if decade1 == 0 and date1 < date2 then return "przełom pierwszej i drugiej dekady " .. century1 .. " wieku" elseif decade1 == 10 and date1 < date2 then return "przełom drugiej dekady i lat 20. " .. century1 .. " wieku" elseif decade1 == 90 and decade2 == 0 then return "przełom ostatniej dekady " .. century1 .. " i pierwszej dekady " .. century2 .. " wieku" else return "przełom lat " .. decade1 .. ". i " .. decade2 .. ". " .. century1 .. " wieku" end end, qu='qhipaq $date1 watakuna ñawpaq $date2 watakunapas', ru='конец $date1-х — начало $date2-х гг.', sv='sena $date1-talet och tidiga $date2-talet', sk=function(date1, date2, state) local decade1 = math.floor((date1 % 100) / 10) local decade2 = math.floor((date2 % 100) / 10) local century1 = math.floor( date1 / 100) + 1 local century2 = math.floor( date2 / 100) + 1 if decade1 == 0 and date1 < date2 then return "prelom 1. a 2. desaťročia " .. century1 .. ". storočia" elseif decade2 == 0 and date2 < date1 then return "prelom 9. a 10. desaťročia " .. century1 .. ". storočia" elseif (decade1 == 9 and date1 < date2) or (decade2 == 9 and date2 < date1) then return "prelom " .. century1 .. ". a " .. century2 .. ". storočia" else return "prelom " .. decade1 .. "0. a " .. decade2 .. "0. rokov " .. century1 .. ". storočia" end end, sl='pozna $date1-ta in zgodnja $date2-ta leta', uk='кінець $date1-х — початок $date2-х рр.', ur='اخیر دہائی $date1 و ابتدا دہائی $date2', vi='bước sang thập niên $date2', ['zh-hans']='$date1年代末$date2年代初', ['zh-hant']='$date1年代末$date2年代初', default=function(date1, date2, state) return {conj='and', adj1 = 'late', unit1='decade', era1=state.era1, adj2 = 'early', unit2='decade', era2=state.era2} end, }, -- $date1 - original date - 1 -- $date2 - original date -- if era is BC than $date1 and $date2 are swapped ['turn of the year']={ nDates = 2, ['be-tarask']='канец $date1 — пачатак $date2 г.', bn='$date1-এর শেষে এবং $date2-এর শুরুতে', cs='přelom roku $date1/$date2', de='Jahreswechsel $date1/$date2', en='late $date1 and early $date2', ['en-gb']='late $date1 and early $date2', ['en-ca']='late $date1 and early $date2', eo='jarŝanĝo $date1 / $date2', es='finales de $date1 y principios de $date2', fa='اواخر $date1 و اوایل $date2', fi='vuoden $date1 loppu ja vuoden $date2 alku', fr='fin $date1 / début $date2', hsb='kónc $date1/spočatk $date2', ja='$date1年末から$date2年初め', ml='$date1 അവസാനവും $date2 ആദ്യവും', nb='årsskiftet $date1/$date2', nl='eind $date1 en begin $date2', nn='årsskiftet $date1/$date2', pl='koniec $date1 i początek $date2 roku', qu="$date1 qhipaq p'unchawkuna $date2 ñawpaq p'unchawkunapas", ru='конец $date1 — начало $date2 г.', sk='prelom rokov $date1/$date2', sl='konec leta $date1 in začetek $date2', sv='sena $date1 och tidiga $date2', uk='кінець $date1 — початок $date2 р.', ur='اخیر $date1 و ابتدا $date2', vi='bước sang năm $date2', ['zh-hans']='$date1年末$date2年初', ['zh-hant']='$date1年末$date2年初', default=function(date1, date2, state) return {conj='and', adj1 = 'late', unit1='year', era1=state.era1, adj2 = 'early', unit2='year', era2=state.era2} end, }, ['circa2'] = { nDates = 2, ar='حوالي $date1 – $date2', ['be-tarask']={'прыкладна з $date1 да прыкладна $date2', case={'gen','gen'}}, bn='প্রায় $date1 এবং প্রায় $date2-এর মাঝামাঝি', ca='cap a $date1 o $date2', cs='cca $date1 až $date2', da='ca. mellem $date1 og $date2', de='etwa $date1 bis $date2', el='περίπου μεταξύ $date1 και $date2', en='between circa $date1 and circa $date2', ['en-gb']='between circa $date1 and circa $date2', ['en-ca']='between circa $date1 and circa $date2', eo='proksimume de $date1 ĝis $date2', es='hacia $date1 o $date2', et='ca. $date1 ja $date2 vahel', fa='حدوداً بین $date1 و $date2', fi={function(date1, date2, state) return 'suunnilleen ' .. decYear(state,'fi','xxx') .. '$date1 ja $date2 välisenä aikana' end, case={'gen','gen'}}, fr='vers $date1-$date2', gl='entre $date1 e $date2', he='בסביבות ה- $date1 עד $date2', hr='oko između $date1 i $date2', hsb={'wot něhdźe $date1 do něhdźe $date2', case={'gen','gen'}}, hu='$date1 és $date2 között', it='tra il $date1 e il $date2 circa', ja='$date1頃と$date2頃の間', mk='~ помеѓу $date1 и $date2', ml='ഉദ്ദേശം $date1-നും ഉദ്ദേശം $date2-നും ഇടയ്ക്ക്', nb='ca. mellom $date1 og $date2', nn='ca. mellom $date1 og $date2', nds='üm un bi $date1 bet $date2', nl='ca. $date1 tot $date2', no='ca. mellom $date1 og $date2', pl={'od około $date1 do około $date2', case={'gen','gen'}}, pt='cerca de $date1 a $date2', qu="$date1 p'unchawmantachá $date2 p'unchawkamachá", ro='circa $date1 – $date2', ru={'с около $date1 до, примерно, $date2', case={'gen','gen'}}, sk='cca $date1 až $date2', sl={function(date1, date2, state) return 'ok. ' .. decYear(state,'sl','gen') .. ' $date1 do $date2' end, case={'gen','gen'}}, sv='c:a $date1 till $date2', uk={'з близько $date1 до, приблизно, $date2', case={'gen','gen'}}, ur='قریباً بین $date1 و $date2', vi='giữa vào khoảng $date1 và vào khoảng $date2', ['zh-hans']='约$date1~$date2间', ['zh-hant']='約$date1~$date2間', default=function(date1, date2, state) return {conj='between', adj1 = 'circa', unit1=state.unit1, era1=state.era1, adj2 = 'circa', unit2=state.unit2, era2=state.era2} end, }, -- ========================================== -- === Eras and calendars =================== -- ========================================== ['julian'] = { nDates = 2, ar='$date1 ($date2 حسب [[w:ar:التقويم اليولياني|تقويم يولياني]])', ['be-tarask']='$date1 ($date2 паводле [[w:be-tarask:Юліянскі каляндар|ст. ст.]]', bn='$date1 (জুলিয়ান ক্যালেন্ডারে $date2)', ca='$date1 ($date2 segons el [[w:ca:Calendari julià|calendari julià]])', cs='$date1 ($date2 podle [[w:cs:Juliánský kalendář|juliánského kalendáře]])', da='$date1 ($date2 [[w:da:Julianske kalender|Julianske kalender]])', de='$date1 ($date2 [[w:de:Julianischer Kalender|Julianischer Kalender]])', el='$date1 ($date2 με το [[w:el:Ιουλιανό ημερολόγιο|Ιουλιανό ημερολόγιο]])', en='$date1 ($date2 in [[w:Old Style and New Style dates|Julian calendar]])', ['en-gb']='$date1 ($date2 in [[w:Old Style and New Style dates|Julian calendar]])', ['en-ca']='$date1 ($date2 in [[w:Old Style and New Style dates|Julian calendar]])', fa='$date1 ($date2 در [[w:fa:Old Style and New Style dates|تقویم جولیان]])', eo='$date1 ($date2 laŭ la [[w:eo:Julia kalendaro|Julia Kalendaro]])', es='$date1 ($date2 según el [[w:es:Calendario juliano|calendario juliano]])', et='$date1 ($date2 [[w:et:Juliuse kalender|Juliuse kalendris]])', fi='$date1 ($date2 [[w:fi:juliaaninen kalenteri|juliaanisen kalenterin]] mukaan)', fr='$date1 ($date2 dans le [[w:fr:calendrier julien|calendrier julien]])', gl='$date1 ($date2 segundo o [[w:gl:Calendario xuliano|calendario xuliano]])', hu='$date1 ($date2 a [[w:hu:Julianus-naptár|Julián naptárban]])', hr='$date1 ($date2 prema [[w:hr:Julijanski kalendar|julijanskom kalendaru]])', he='$date1 ($date2 ב[[w:he:לוח היוליאני|הלוח היוליאני]])', hsb='$date1 ($date2 po julianiskej protyce)', it='$date1 ($date2 secondo il [[w:it:Calendario giuliano|calendario giuliano]])', ja='$date1([[w:ja:ユリウス暦|ユリウス暦]] $date2)', mk='$date1 ($date2 по [[w:mk:Јулијански календар|Јулијанскиот календар]])', ml='$date1 ([[w:ml:ജൂലിയൻ കാലഗണനാരീതി|ജൂലിയൻ വർഷത്തിൽ]] $date2)', ml='$date1 ([[w:Old Style and New Style dates|ജൂലിയൻ കാലഗണനാരീതിയിൽ]] $date2)', nds='$date1 ($date2 na’n [[w:nds:Juliaansch Klenner|Juliaanschen Klenner]])', nb='$date1 ($date2 etter [[w:no:Juliansk kalender|den julianske kalenderen]])', nn='$date1 ($date2 etter [[w:no:Juliansk kalender|den julianske kalenderen]])', nl='$date1 ($date2 volgens de [[w:nl:Juliaanse kalender|Juliaanse kalender]])', no='$date1 ($date2 etter [[w:no:Juliansk kalender|den julianske kalender]])', pl='$date1 ($date2 wedle [[w:pl:Daty nowego i starego porządku|kalendarza juliańskiego]])', pt='$date1 ($date2 no [[w:pt:Mudança para o calendário gregoriano|calendário juliano]])', qu='$date1 ($date2 [[w:qu:Hulyanu kalindaryu|Hulyanu kalindaryupi]])', ro='$date1 ($date2 în [[w:ro:calendarul iulian|calendarul iulian]])', ru='$date1 ($date2 [[w:ru:Юлианский календарь|по ст. ст.]])', sk='$date1 ($date2 podľa [[w:sk:Juliánsky kalendár|juliánskeho kalendára]])', sl='$date1 ($date2 po [[w:sl:Julijanski koledar|julijanskem koledarju]])', sv='$date1 ($date2 [[w:sv:Gamla stilen|gamla stilen]])', uk='$date1 ($date2 [[w:uk:Юліанський календар|Юліанський календар]])', ur='$date1 ($date2 حسب [[w:ur:نئے اور پانے انداز کی تاریخ|جولین تقویم]])', vi='$date1 ($date2 trong [[w:vi:Lịch Julius|lịch Juliêng]])', ['zh-hans']='$date1([[w:Old Style and New Style dates|格里历]]$date2)', ['zh-hant']='$date1([[w:Old Style and New Style dates|格里曆]]$date2)', default='<span style="color:red">$date1 ($date2 in [[w:Old Style and New Style dates|Julian calendar]])</span>', }, ['ad'] = { nDates = 1, ['be-tarask']='$date1 [[w:be-tarask:ад Нараджэньня Хрыста|пасьля Н. Х.]]', bn ='$date1 খ্রিস্টাব্দ', ca ='$date1 dC', cs ='$date1 n. l.', de ='$date1 n. Chr.', el ='$date1 μ.Χ.', en ='$date1 [[w:en:Anno Domini|AD]]', ['en-gb'] ='$date1 [[w:en:Anno Domini|AD]]', ['en-ca'] ='$date1 [[w:en:Anno Domini|AD]]', eo ='$date1 pK', es ='$date1 [[w:es:Anno Dómini|d. C.]]', fa ='$date1 م.', fi ='$date1 jaa.', fr ='$date1 ap. J.-C.', gl ='$date1 d.C.', he ='$date1 לספירה', hu ='i. sz. $date1', hr ='$date1 p. Kr.', hsb='$date1 po Chr.', it ='$date1 d.C.', ja ='西暦$date1', mk ='$date1 н.е.', ml ='ക്രി.വ. $date1', nb ='$date1 e.Kr.', nl ='$date1 n.Chr.', nn ='$date1 e.Kr.', no ='$date1 e.Kr.', pl ='$date1 [[w:pl:naszej ery|n.e.]]', ro ='$date1 d.Hr.', ru ='$date1 н. э.', sk ='$date1 po Kr.', sl ='$date1 n. št.', sv ='$date1 [[w:sv:Efter Kristus|e.Kr.]]', uk ='$date1 н. е.', ur ='$date1 ء', vi ='$date1 [[w:vi:Công Nguyên|CN]]', ['zh-hans'] ='[[w:zh:基督纪年|公元]]$date1', ['zh-hant'] ='[[w:zh:基督纪年|公元]]$date1', default='<span style="color:red">$date1 [[w:en:Anno Domini|AD]]</span>', }, ['ah'] = { nDates = 1, ['be-tarask']='$date1 [[w:be-tarask:Ісламскі каляндар|хіджры]]', bn ='$date1 [[w:bn:ইসলামি বর্ষপঞ্জি|হিজরী]]', cs ='$date1 [[w:cs:Anno Hegirae|AH]]', en ='$date1 [[w:en:Hijri year|AH]]', ['en-gb'] ='$date1 [[w:en:Hijri year|AH]]', ['en-ca'] ='$date1 [[w:en:Hijri year|AH]]', eo ='$date1 post la [[w:eo:Heĝiro|Heĝiro]]', fa ='$date1 ه.ق.', fr ='$date1 [[w:fr:Calendrier musulman|A.H.]]', he ="$date1 [[w:he:הלוח המוסלמי|להג'רה]]", hr ='$date1 god. hidžre', ja ='[[w:ja:ヒジュラ暦|ヒジュラ暦]]$date1', ml ='$date1 [[w:ml:ഇസ്‌ലാമിക കലണ്ടർ|ഹിജ്റ]]', nb ='$date1 [[w:no:Den muslimske kalenderen|A.H.]]', nn ='$date1 [[w:nn:Den muslimske kalenderen|A.H.]]', pl ='$date1 [[w:pl:Kalendarz muzułmański|A.H.]]', ru ='$date1 [[w:ru:Исламский календарь|хиджры]]', sk ='$date1 [[w:sk:A. H.|A. H.]]', sv ='$date1 [[w:sv:Muslimska kalendern|A.H.]]', uk ='$date1 [[w:uk:Мусульманський календар|хіджри]]', ur ='$date1 ھ', vi ='$date1 [[w:vi:Lịch Hồi giáo|AH]]', ['zh-hans'] ='[[w:en:Hijri year|Hijri历]]$date1', ['zh-hant'] ='[[w:en:Hijri year|Hijri曆]]$date1', default='<span style="color:red">$date1 [[w:en:Hijri year|AH]]</span>', }, ['bc'] = { nDates = 1, ar ='$date1 ق.م.', ['be-tarask']='$date1 [[w:be-tarask:да Нараджэньня Хрыста|да Н. Х.]]', bn ='খ্রিস্টপূর্ব $date1', ca ='$date1 [[w:ca:Abans de Crist|aC]]', cs ='$date1 [[w:cs:Př. n. l.|př. n. l.]]', de ='$date1 [[w:de:v. Chr.|v. Chr.]]', el ='$date1 π.Χ.', en ='$date1 [[w:en:Ante Christum Natum|BC]]', ['en-gb'] ='$date1 [[w:en:Ante Christum Natum|BC]]', ['en-ca'] ='$date1 [[w:en:Ante Christum Natum|BC]]', eo ='$date1 aK', es ='$date1 [[w:es:Antes de Cristo|a. C.]]', fa ='$date1 پ.م.', fi ='$date1 eaa.', fr ='$date1 av. J.-C.', gl ='$date1 [[w:gl:Antes de Cristo|a.C.]]', he ='$date1 לפני הספירה', hr ='$date1 [[w:hr:Pr. Kr.|pr. Kr.]]', hsb='$date1 do Chr.', hu ='i. e. $date1', it ='$date1 [[w:it:Avanti Cristo|a.C.]]', ja ='[[w:ja:紀元前|紀元前]]$date1', mk ='$date1 п.н.е.', ml ='ക്രി.മു. $date1', nds='$date1 vör uns Tied', nb ='$date1 f.Kr.', nl ='$date1 v.Chr.', nn ='$date1 f.Kr.', no ='$date1 f.Kr.', pl ='$date1 [[w:pl:przed naszą erą|p.n.e.]]', ro ='$date1 î.Hr.', ru ='$date1 до н. э.', sk ='$date1 [[w:sk:Pred Kristom|pred Kr.]]', sl ='$date1 pr. n. št.', sv ='$date1 [[w:sv:Före Kristus|f.Kr.]]', uk ='$date1 до н. е.', ur ='$date1 ق م', vi ='$date1 [[w:vi:Trước Công Nguyên|TCN]]', ['zh-hans'] ='[[w:zh:基督纪年|公元前]]$date1', ['zh-hant'] ='[[w:zh:基督纪年|公元前]]$date1', default='<span style="color:red">$date1 [[w:en:Ante Christum Natum|BC]]</span>', }, ['bp'] = { nDates = 1, ar ='$date1 [[w:ar:قبل الحاضر|قبل الحاضر]]', be ='$date1 [[w:be-tarask:Да нашых дзён|Да нашых дзён]]', ['be-tarask']='$date1 [[w:be-tarask:Да нашых дзён|Да нашых дзён]]', bn ='বর্তমানের আগে $date1', ca ='$date1 [[w:ca:BP (unitat de temps)|BP]]', cs ='$date1 [[w:cs:B. P.|B. P.]]', de ='$date1 [[w:de:Before Present|BP]]', el ='$date1 [[w:en:Before Present|BP]]', en ='$date1 [[w:en:Before Present|BP]]', ['en-gb'] ='$date1 [[w:en:Before Present|BP]]', ['en-ca'] ='$date1 [[w:en:Before Present|BP]]', eo='$date1 [[w:eo:Before Present|BP]]', es ='$date1 [[w:es:Before Present|BP]]', et ='$date1 [[w:et:Before Present|BP]]', fa ='$date1 پیش از اکنون', fi ='$date1 [[w:fi:BP (ajanlasku)|BP]]', fr ='$date1 [[w:fr:Avant le présent|avant le présent]]', hr ='$date1 [[w:hr:BP|prije današnjice]]', it ='$date1 [[w:it:Before Present|BP]]', ja ='$date1 [[w:ja:BP (年代測定)|BP]]', lv ='$date1 [[w:lv:Pirms mūsdienām|Pirms mūsdienām]]', mk ='$date1 [[w:mk:Пред сегашноста|п.с.]]', ml ='[[w:en:Before Present|ഇന്നത്തെ കാലത്തിനു മുമ്പ്]] $date1', nb ='$date1 [[w:no:Before Present|BP]]', nn ='$date1 [[w:nn:Before Present|BP]]', nl ='$date1 [[w:nl:Before Present|BP]]', no ='$date1 [[w:no:Before Present|BP]]', pl ='$date1 [[w:pl:Before Present|BP]]', pt ='$date1 [[w:pt:Antes do Presente|Antes do Presente]]', ru ='$date1 [[w:ru:До настоящего времени|до н. в.]]', sk ='$date1 [[w:sk:BP (čas)|BP]]', sl ='$date1 [[w:sl:pred sedanjostjo|p. s.]]', sr ='$date1 [[w:sr:Прије садашњости|Прије садашњости]]', sv ='$date1 [[w:sv:B.P.|B.P.]]', uk ='$date1 [[w:uk:Років тому|Років тому]]', ur ='$date1 قبل حاضر', vi ='$date1 [[w:en:Before Present|trước ngày nay]]', ['zh-hans'] ='[[w:zh:距今|距今]]$date1', ['zh-hant'] ='[[w:zh:距今|距今]]$date1', default='<span style="color:red">$date1 [[w:en:Before Present|BP]]</span>', }, } -- List of possible string inputs to "adj", "junction", "era", and "precision" fields -- Each term is matched to its synonym and the role: a - adjective, e - era -- p - precision, c - calendar and combos, r - certainty and j - conjunction p.Synonyms = { -- adjectives and prepositions ['quarter1']={'1quarter','a'}, ['1stquarter']={'1quarter','a'}, ['1st quarter']={'1quarter','a'}, ['first quarter']={'1quarter','a'}, ['1quarter']={'1quarter','a'}, ['quarter2']={'2quarter','a'}, ['2ndquarter']={'2quarter','a'}, ['2nd quarter']={'2quarter','a'}, ['second quarter']={'2quarter','a'}, ['2quarter']={'2quarter','a'}, ['quarter3']={'3quarter','a'}, ['3rdquarter']={'3quarter','a'}, ['3rd quarter']={'3quarter','a'}, ['third quarter']={'3quarter','a'}, ['3quarter']={'3quarter','a'}, ['quarter4']={'4quarter','a'}, ['4thquarter']={'4quarter','a'}, ['4th quarter']={'4quarter','a'}, ['fourth quarter']={'4quarter','a'}, ['4quarter']={'4quarter','a'}, ['spring']={'spring','a'}, ['summer']={'summer','a'}, ['fall']={'autumn','a'}, ['autumn']={'autumn','a'}, ['winter']={'winter','a'}, ['beginning']={'early','a'}, ['early']={'early','a'}, ['1half']={'firsthalf','a'}, ['1sthalf']={'firsthalf','a'}, ['1st half']={'firsthalf','a'}, ['firsthalf']={'firsthalf','a'}, ['first half']={'firsthalf','a'}, ['end']={'late','a'}, ['late']={'late','a'}, ['middle']={'mid','a'}, ['mid']={'mid','a'}, ['2half']={'secondhalf','a'}, ['2ndhalf']={'secondhalf','a'}, ['2nd half']={'secondhalf','a'}, ['secondhalf']={'secondhalf','a'}, ['second half']={'secondhalf','a'}, ['<']={'before','a'}, ['b']={'before','a'}, ['before']={'before','a'}, ['>']={'after','a'}, ['a']={'after','a'}, ['after']={'after','a'}, ['~']={'circa','a'}, ['c']={'circa','a'}, ['ca']={'circa','a'}, ['circa']={'circa','a'}, ['by']={'by','a'}, ['from']={'from','a'}, ['until']={'until','a'}, ['uncertain']={'uncertain','r'}, ['possibly']={'possibly','r'}, ['probably']={'probably','r'}, ['presumably']={'presumably','r'}, -- eras ['bc']={'bc','e'}, ['bce']={'bc','e'}, ['before present']={'bp','e'}, ['bp']={'bp','e'}, ['ce']={'ad','e'}, ['ad']={'ad','e'}, ['ah']={'ah','e'}, -- conjunctions ['&']={'and','j'}, ['and']={'and','j'}, ['or']={'or','j'}, ['-']={'from-until','j'}, ['–']={'from-until','j'}, ['from-until']={'from-until','j'}, ['between']={'between','j'}, ['circa2']={'circa2','a'}, -- precision ['day']={'day','p'}, ['month']={'month','p'}, ['year']={'year','p'}, ['s']={'decade','p'}, ['decade']={'decade','p'}, ['century']={'century','p'}, ['millennium']={'millennium','p'}, ['million']={'million','p'}, ['billion']={'billion','p'}, -- calendar ['julian']={'julian','c'}, ['islamic']={'islamic','c'}, ['hijri']={'islamic','c'}, -- "Turn of the ..." options are a combos which will be handled in similar way as calendars ['turn of the century']={'turn of the century','c'}, ['century turn']={'turn of the century','c'}, ['turn of the decade']={'turn of the decade','c'}, ['decade turn']={'turn of the decade','c'}, ['turn of the year']={'turn of the year','c'}, ['year turn']={'turn of the year','c'}, -- other dates which will be handled in similar way as calendars ['?']={'unknown','c'}, ['unknown']={'unknown','c'}, ['unknown year']={'year unknown','c'}, ['year unknown']={'year unknown','c'}, } return p 4v7usasdo7wjfkksm29h8yxtd6em2e0 803608 803607 2019-02-25T21:49:42Z en>Pppery 0 Re-lazifying 803608 Scribunto text/plain --[[ __ __ _ _ ___ _ ___ __ _ _ _ | \/ | ___ __| |_ _| | ___ _|_ _/ |( _ ) _ __ / /__ ___ _ __ ___ _ __ | | _____ __ __| | __ _| |_ ___ | |\/| |/ _ \ / _` | | | | |/ _ (_)| || |/ _ \| '_ \ / / __/ _ \| '_ ` _ \| '_ \| |/ _ \ \/ / / _` |/ _` | __/ _ \ | | | | (_) | (_| | |_| | | __/_ | || | (_) | | | |/ / (_| (_) | | | | | | |_) | | __/> < | (_| | (_| | || __/ |_| |_|\___/ \__,_|\__,_|_|\___(_)___|_|\___/|_| |_/_/ \___\___/|_| |_| |_| .__/|_|\___/_/\_\ \__,_|\__,_|\__\___| |_| This code was copied from Commons please request changes there. Maintainers: *Jarekt ]] local function Ordinal(...) return require('Module:Ordinal-cd')._Ordinal(...) end local function Roman(...) return require('Module:Roman')._Numeral(...) end -- ========================================================================== -- === Local Helper functions ============================================== -- ========================================================================== -- form of French "of" based on "precision", which is wikibase compatible date precision format: -- 6=millennium, 7=century, 8=decade, 9=year, 10=month, 11=day -- 1 2 3 4 5 mill cent deca year month day local frOf = {'du','du','du','du','du', 'du', 'du', 'de', 'de', 'de', 'du'} local function decYear(state,lang,case) --Declension of word "year" in several languages if state.precision == 9 then yearCase = { fi={xxx='vuosien', gen='vuoden', ill='vuoteen', ptv='vuotta', ela='vuodesta', ine='vuonna'}, sl={insP='letoma', ins='letom', gen='leta', loc='letu'}, hsb={insP='lětami', ins='lětom', gen='lěta', loc='lěće'} } return yearCase[lang][case] .. ' ' else return '' end end local function decCentury(lang,case) --Declension of word "century" in several languages centuryCase = { pl={nom='wiek', gen='wieku', ins='wiekiem', loc='wieku', default = 'wiek'}, hsb={nom='lětstotk', gen='lětstotka', ins='lětstotkom', loc='lětstotku', default = 'lětstotk'} } return centuryCase[lang][case] or centuryCase[lang]['default'] end local function decMillennium(lang,case) --Declension of word "century" in several languages millenniumCase = { pl={nom='tysiąclecie', gen='tysiąclecia', ins='tysiącleciem', loc='tysiącleciu', default = 'tysiąclecie'}, hsb={nom='lěttysac', gen='lěttysaca', ins='lěttysacom', loc='lěttysacu', default = 'lěttysac'} } return millenniumCase[lang][case] or millenniumCase[lang]['default'] end -- ========================================================================== -- === Output data structure =============================================== -- ========================================================================== local p = {} --[[ Multiple formats are possible: 1) string with key words "$date1" and "$date2" which will be replaced later by the date strings 2) function which can be used to create output string * Function INPUTS: - date1 and date2 string with date strings. "$date1" and "$date2" can also be used which will be replaced latter - state is a table with full description of the phrase we are trying to construct. It includes state.precision1 (and 2) which is wikibase compatible date precision format: 6=millennium, 7=century, 8=decade, 9=year, 10=month, 11=day 3) table with following fields: * table[1] is either string or a function * table.case is a 2 element table with Grammatical cases used by given language to alter month name or other adjective related to date # 1 and 2 ]] p.Translations = { -- ========================================== -- === Two date phrases ===================== -- ========================================== ['from-until'] = { nDates = 2, ar='من $date1 إلى $date2', ['be-tarask']={'з $date1 да $date2', case={'gen','gen'}}, bn='$date1 থেকে $date2 পর্যন্ত', ca='$date1 fins $date2', cs='$date1 až $date2', da='fra $date1 til $date2', de='$date1 bis $date2', el='από $date1 έως $date2', en='from $date1 until $date2', ['en-gb']='from $date1 until $date2', ['en-ca']='from $date1 until $date2', eo='de $date1 al $date2', es='de $date1 a $date2', et='$date1 kuni $date2', fa='از $date1 تا $date2', fi='$date1–$date2', -- välisenä aikana fr=function(date1, date2, state) -- could actually be better should be "d'octobre à mars 2013' not "de octore 2013 à mars 2013" if state.precision > 10 then -- precision > month return 'du $date1 au $date2' else -- "DE septebmbre" / "D'octobre" return require("Module:Linguistic").of(date1, 'fr') .. " à $date2" end end, gl='de $date1 a $date2', --Template:From until used 'desde $date1 ata $date2' he='מ- $date1 עד $date2', hr='od $date1 do $date2', hsb={'wot $date1 do $date2', case={'gen','gen'}}, hu='$date1 – $date2', it='dal $date1 al $date2', ja='$date1から$date2まで', mk='од $date1 до $date2', ml='$date1 മുതൽ $date2 വരെ', nb='fra $date1 til $date2', nds='$date1 bet $date2', nn='fra $date1 til $date2', nl='$date1 tot $date2', no='fra $date1 til $date2', pl={'od $date1 do $date2', case={'gen','gen'}}, ro='din $date1 până în $date2', pt='de $date1 a $date2', qu="$date1 p'unchawmanta $date2 p\'unchawkama", ru={'с $date1 по $date2', case={'gen','gen'}}, sk={'od $date1 do $date2', case={'gen','gen'}}, sl={'od $date1 do $date2', case={'gen','gen'}}, sv='från $date1 till $date2', uk={'з $date1 до $date2', case={'gen','gen'}}, ur='از $date1 تا $date2', vi='từ $date1 đến $date2', ['zh-hans']='从$date1到$date2', ['zh-hant']='從$date1到$date2', default='<span style="color:red">$date1 - $date2</span>', }, ['between']={ nDates = 2, ar='بين $date1 و $date2', ['be-tarask']={'між $date1 і $date2', case={'ins','ins'}}, bn='$date1 এবং $date2-এর মাঝামাঝি', ca='entre $date1 i $date2', cs={'mezi $date1 a $date2', case={'ins','ins'}}, da='mellem $date1 og $date2', de='zwischen $date1 und $date2', el='μεταξύ $date1 και $date2', en='between $date1 and $date2', ['en-gb']='between $date1 and $date2', ['en-ca']='between $date1 and $date2', eo='inter $date1 kaj $date2', es='entre $date1 y $date2', et='$date1 ja $date2 vahel', fa='بین $date1 و $date2', fi={function(date1, date2, state) return decYear(state,'fi','xxx') .. '$date1 ja $date2 välisenä aikana' end, case={'gen','gen'}}, fr='entre $date1 et $date2', gl='entre $date1 e $date2', he='בין $date1 ו- $date2', hr='između $date1 i $date2', hsb={'mjez $date1 a $date2', case={'ins','ins'}}, hu='$date1 és $date2 között', hi='$date1 और $date2 के बीच', it='tra il $date1 e il $date2', ja='$date1と$date2の間', mk='помеѓу $date1 и $date2', ml='$date1, $date2 എന്നിവയ്ക്കിടയ്ക്ക്', nb='mellom $date1 og $date2', nds='twischen $date1 un $date2', nn='mellom $date1 og $date2', nl='tussen $date1 en $date2', no='mellom $date1 og $date2', pl={'między $date1 a $date2', case={'ins','ins'}}, ro='între $date1 și $date2', pt='entre $date1 e $date2', qu="$date1 p'unchawpa $date2 p'unchawpapas chawpin", ru={'между $date1 и $date2', case={'ins','ins'}}, sk={'medzi $date1 a $date2', case={'ins','ins'}}, sl={function(date1, date2, state) return 'med ' .. decYear(state,'sl','insP') .. '$date1 in $date2' end, case={'ins','ins'}}, sv='mellan $date1 och $date2', uk={'між $date1 та $date2', case={'ins','ins'}}, ur='بین $date1 و $date2', vec='tra el $date1 e el $date2', vi='giữa $date1 và $date2', ['zh-hans']='在$date1到$date2之间', ['zh-hant']='在$date1到$date2之間', default='<span style="color:red">$date1 - $date2</span>', }, ['or'] = { nDates = 2, ar='$date1 أو $date2', ['be-tarask']='$date1 ці $date2', bn='$date1 অথবা $date2', ca='$date1 o $date2', cs='$date1 nebo $date2', da='$date1 eller $date2', de='$date1 oder $date2', el='$date1 ή $date2', en='$date1 or $date2', ['en-gb']='$date1 or $date2', ['en-ca']='$date1 or $date2', eo='$date1 aŭ $date2', es='$date1 o $date2', et='$date1 või $date2', fa='$date1 یا $date2', fi='$date1 tai $date2', fr='$date1 ou $date2', gl='$date1 ou $date2', he='$date1 או $date2', hr='$date1 ili $date2', hsb='$date1 abo $date2', hu='$date1 vagy $date2', it='$date1 o $date2', ja='$date1または$date2', mk='$date1 или $date2', ml='$date1 അഥവാ $date2', nb='$date1 eller $date2', nn='$date1 eller $date2', nds='$date1 oder $date2', nl='$date1 of $date2', no='$date1 eller $date2', pl='$date1 lub $date2', pt='$date1 ou $date2', qu='$date1 icha $date2', ro='$date1 sau $date2', ru='$date1 или $date2', sk='$date1 alebo $date2', sl='$date1 ali $date2', sv='$date1 eller $date2', uk='$date1 або $date2', ur='$date1 یا $date2', th='$date1 หรือ $date2', vi='$date1 hoặc $date2', ['zh-hans']='$date1或$date2', ['zh-hant']='$date1或$date2', default=function(date1, date2, state) return require("Module:linguistic").conj({date1, date2}, state.lang, 'or') end, }, ['and'] = { nDates = 2, ar='$date1 و $date2', ['be-tarask']='$date1 і $date2', bn='$date1 এবং $date2', ca='$date1 i $date2', cs='$date1 a $date2', da='$date1 og $date2', de='$date1 und $date2', el='$date1 και $date2', en='$date1 and $date2', ['en-gb']='$date1 and $date2', ['en-ca']='$date1 and $date2', eo='$date1 kaj $date2', es='$date1 y $date2', et='$date1 ja $date2', fa='$date1 و $date2', fi='$date1 ja $date2', fr='$date1 et $date2', gl='$date1 e $date2', he='$date1 ו- $date2', hu='$date1 és $date2', hsb='$date1 a $date2', it='$date1 e $date2', ja='$date1および$date2', mk='$date1 и $date2', ml='$date1 ഒപ്പം $date2', nb='$date1 og $date2', nn='$date1 og $date2', nds='$date1 un $date2', nl='$date1 en $date2', no='$date1 og $date2', pl='$date1 i $date2', pt='$date1 e $date2', qu='$date1wan $date2pas', ro='$date1 și $date2', ru='$date1 и $date2', sk='$date1 a $date2', sl='$date1 in $date2', sv='$date1 och $date2', uk='$date1 та $date2', ur='$date1 و $date2', vi='$date1 và $date2', ['zh-hans']='$date1和$date2', ['zh-hant']='$date1和$date2', default=function(date1, date2, state) return require("Module:linguistic").conj({date1, date2}, state.lang, 'and') end, }, -- ========================================== -- === Prepositions ========================= -- ========================================== ['by'] = { nDates = 1, ar='حوالي $date1', ['be-tarask']={'да $date1', case={'gen',''}}, bn='$date1 কর্তৃক', ca='vers $date1', cs={'kolem $date1', case={'gen',''}}, da='mod $date1', de='spätestens $date1', el='έως $date1', en='by $date1', ['en-gb']='by $date1', ['en-ca']='by $date1', eo='ne post $date1', es='para $date1', et='hiljemalt $date1', fa='توسط $date1', fi={function(date1, state) return decYear(state,'fi','ill') .. '$date1 mennessä' end, case={'ill',''}}, fr='$date1 (au plus tard)', gl='cara a $date1', he='עד $date1', hr='do $date1', hsb={'do $date1', case={'gen',''}}, it='entro il $date1', ja='$date1まで', mk='кон $date1', ml='$date1-ൽ', nb='innen $date1', nn='innan $date1', nds='$date1 oder vördem al', nl='tegen $date1', no='innen $date1', pl={'przed $date1', case={'ins',''}}, pt='por $date1', qu="$date1 p'unchawkama", ro='de $date1', ru={'к $date1', case={'dat',''}}, sk={'okolo $date1', case={'gen',''}}, sl={function(date1, state) return 'najpozneje ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='senast $date1', uk={'до $date1', case={'dat',''}}, ur='توسط $date1', vi='vào $date1', -- need to insert "đã" somewhere later in the sentence --> ['zh-hans']='$date1前后', ['zh-hant']='$date1前后', default='<span style="color:red">by $date1</span>', }, ['before'] = { nDates = 1, ar='قبل $date1', ['be-tarask']={'перад $date1', case={'ins',''}}, bn='$date1-এর আগে', bg='Преди $date1', ca='abans $date1', cs={'před $date1', case={'ins',''}}, da='før $date1', de='vor $date1', el='πριν από $date1', en='before $date1', ['en-gb']='before $date1', ['en-ca']='before $date1', eo='antaŭ $date1', es='antes de $date1', et='enne $date1', fa='پیش از $date1', fi={function(date1, state) return 'ennen ' .. decYear(state,'fi','ptv') .. '$date1' end, case={'ptv',''}}, fr='avant $date1', gl='antes do $date1', he='לפני $date1', hr='prije $date1', hsb={'do $date1', case={'gen',''}}, hu='$date1 előtt', it='prima del $date1', ja='$date1より前', mk='пред $date1', ml='$date1-നു മുമ്പ്', nb='før $date1', nn='før $date1', nds='vör $date1', nl='voor $date1', no='før $date1', pl={'przed $date1', case={'ins',''}}, pt='antes de $date1', qu="$date1 p'unchaw ñawpan", ro='înainte de $date1', ru={'ранее $date1', case={'gen',''}}, sk={'pred $date1', case={'ins',''}}, sl={function(date1, state) return 'pred ' .. decYear(state,'sl','ins') .. ' $date1' end, case={'ins',''}}, sv='före $date1', uk={'до $date1', case={'gen',''}}, ur='قبل $date1', vi='trước $date1', ['zh-hans']='在$date1之前', ['zh-hant']='在$date1之前', default='<span style="color:red">before $date1</span>', }, ['from'] = { nDates = 1, ar='من $date1', ['be-tarask']={'з $date1', case={'gen',''}}, bn='$date1 থেকে', cs={'od $date1', case={'gen',''}}, de='Seit $date1', el='από $date1', en='from $date1', ['en-gb']='from $date1', ['en-ca']='from $date1', eo='ekde $date1', es='desde $date1', et='alates $date1', fa='از $date1', fi={function(date1, state) return decYear(state,'fi','ela') .. '$date1 alkaen' end, case={'ela',''}}, fr= function(date1, state) return 'à partir '.. frOf[state.precision] ..' $date1' end, gl='desde o $date1', he='מ- $date1', hr='od $date1', hsb={'wot $date1', case={'gen',''}}, it='dal $date1', ja='$date1から', nds='vonaf $date1', mk='од $date1', nb='fra $date1', nn='frå $date1', ml='$date1 മുതൽ', nl='vanaf $date1', no='fra $date1', pl={'od $date1', case={'gen',''}}, qu="$date1 p'unchawmanta", ro='din $date1', ru={'С $date1', case={'gen',''}}, sk={'od $date1', case={'gen',''}}, sl={function(date1, state) return 'od ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='från $date1', uk={'з $date1', case={'gen',''}}, ur='از $date1', vi='từ $date1', ['zh-hans']='自$date1', ['zh-hant']='自$date1', default='<span style="color:red">from $date1</span>', }, ['until'] = { nDates = 1, ['be-tarask']={'усутыч да $date1', case={'gen',''}}, bn='$date1 পর্যন্ত', cs={'do $date1', case={'gen', ''}}, de='bis $date1', en='until $date1', ['en-gb']='until $date1', ['en-ca']='until $date1', eo='ĝis $date1', es='hasta $date1', fr=function(date1, state) if state.precision > 10 then -- precision > month return "jusqu'au $date1" else return "jusqu'à $date1" end end, hr='do $date1', hsb={'do $date1', case={'gen',''}}, it='al $date1', nb='til $date1', nl='tot $date1', nn='til $date1', ml='$date1 വരെ', pl={'do $date1', case={'gen',''}}, ru={'вплоть до $date1', case={'gen',''}}, sk={'do $date1', case={'gen', ''}}, uk={'аж до $date1', case={'gen',''}}, ur='تک $date1', ['zh-hans']='直到$date1', ['zh-hant']='直到$date1', default='<span style="color:red">until $date1</span>', }, ['after'] = { nDates = 1, ar='بعد $date1', ['be-tarask']={'пасьля $date1', case={'gen',''}}, bn='$date1-এর পরে', ca='després de $date1', cs={'po $date1', case={'loc',''}}, de='nach $date1', da='efter $date1', el='μετά από $date1', en='after $date1', ['en-gb']='after $date1', ['en-ca']='after $date1', eo='post $date1', es='después de $date1', et='pärast $date1', fa='پس از $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 jälkeen' end, case={'gen',''}}, fr='après $date1', gl='despois do $date1', he='לאחר $date1', hr='nakon $date1', hu='$date1 után', hsb={'po $date1', case={'loc',''}}, it='dopo il $date1', ja='$date1より後', mk='по $date1', ml='$date1-നു ശേഷം', nb='etter $date1', nn='etter $date1', nds='na $date1', nl='na $date1', no='etter $date1', pl={'po $date1', case={'loc',''}}, pt='após $date1', qu="$date1 p'unchaw qhipan", ro='după $date1', ru={'после $date1', case={'gen',''}}, sk={'po $date1', case={'loc',''}}, sl={function(date1, state) return 'po ' .. decYear(state,'sl','loc') .. ' $date1' end, case={'loc',''}}, sv='efter $date1', uk={'після $date1', case={'gen',''}}, ur='بعد $date1', vi='sau $date1', ['zh-hans']='在$date1之后', ['zh-hant']='在$date1之後', default='<span style="color:red">after $date1</span>', }, -- ========================================== -- === Adjectives =========================== -- ========================================== ['early'] = { nDates = 1, ar='بداية $date1', ['be-tarask']={'пачатак $date1', case={'gen',''}}, bn='$date1-এর শুরুতে', ca='al principi de $date1', cs={'začátek $date1', case={'gen',''}}, da='tidlig $date1', de='Anfang $date1', el='αρχές του $date1', en='early $date1', ['en-gb']='early $date1', ['en-ca']='early $date1', eo='komence de $date1', es='a principios de $date1', et='$date1 algus', fa='اوایل $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 alku' end, case={'gen',''}}, fr='début $date1', gl='a principios de $date1', he='תחילת $date1', hr='početak $date1', hsb={'spočatk $date1', case={'gen',''}}, hu='$date1 elején', it='inizio del $date1', ja='$date1初頭', mk='почеток на $date1', ml='$date1 ആദ്യപാദം', nb='tidlig $date1', nn='tidleg $date1', nds='Anfang $date1', nl='begin $date1', no='tidlig $date1', pl={'początek $date1', case={'gen',''}}, pt='a princípios de $date1', qu="$date1 ñawpaq p'unchawkunapi", ro='începând cu $date1', ru={'начало $date1', case={'gen',''}}, sk={'začiatok $date1', case={'gen',''}}, sl=function(date1, state)-- could actually be better should be "d'octobre à mars 2013' not "de octore 2013 à mars 2013" if state.precision == 9 then -- precision > month return 'zgodnje leto $date1' else -- "DE septebmbre" / "D'octobre" return 'zgodnji $date1' end end, sv='början av $date1', uk={'початок $date1', case={'gen',''}}, ur='ابتدا $date1', vi='đầu năm $date1', ['zh-hans']='$date1早期', ['zh-hant']='$date1早期', default='<span style="color:red">early $date1</span>', }, ['mid'] = { nDates = 1, ar='منتصف $date1', ['be-tarask']={'сярэдзіна $date1', case={'gen',''}}, bn='$date1-এর মাঝামাঝি', ca='a mitjan $date1', cs={'polovina $date1', case={'gen',''}}, da='midten af $date1', de='Mitte $date1', el='μέσα του $date1', en='mid $date1', ['en-gb']='mid $date1', ['en-ca']='mid $date1', eo='meze de $date1', es='a mediados de $date1', et='$date1 keskel', fa='اواسط $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 puoliväli' end, case={'gen',''}}, fr='mi $date1', gl='a mediados de $date1', he='אמצע $date1', hr='sredina $date1', hsb={'srjedź $date1', case={'gen',''}}, hu='$date1 közepén', it='metà del $date1', ja='$date1半ば', mk='средина на $date1', ml='$date1 മദ്ധ്യം', nb='midten av $date1', nn='midten av $date1', nds='Midd $date1', nl='midden $date1', no='midten av $date1', pl={'środek $date1', case={'gen',''}}, pt='meados de $date1', qu="$date1 chawpin p'unchawkunapi", ro='mijlocul lui $date1', ru={'середина $date1', case={'gen',''}}, sk={'polovica $date1', case={'gen',''}}, sl={function(date1, state) return 'sredina ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='mitten av $date1', uk={'середина $date1', case={'gen',''}}, ur='وسط $date1', vi='giữa năm $date1', ['zh-hans']='$date1中期', ['zh-hant']='$date1中期', default='<span style="color:red">mid $date1</span>', }, ['late'] = { nDates = 1, ar='نهاية $date1', bn='$date1-এর শেষে', ['be-tarask']={'канец $date1', case={'gen',''}}, ca='al final de $date1', cs='konec $date1', da='sen $date1', de='Ende $date1', el='τέλη του $date1', en='late $date1', ['en-gb']='late $date1', ['en-ca']='late $date1', eo='fine de $date1', es='a fines de $date1', et='$date1 lõpp', fa='اواخر $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 loppu' end, case={'gen',''}}, fr='fin $date1', gl='a finais de $date1', he='סוף $date1', hr='kraj $date1', hsb={'kónc $date1', case={'gen',''}}, hu='$date1 végén', it='fine del $date1', ja='$date1末', mk='крајот на $date1', ml='$date1 അന്ത്യപാദം', nb='sent $date1', no='seint $date1', nds='Enn $date1', nl='eind $date1', no='sent $date1', pl={'koniec $date1', case={'gen',''}}, pt='ao final de $date1', qu="$date1 qhipaq p'unchawkunapi", ro='sfârșitul lui $date1', ru={'конец $date1', case={'gen',''}}, sk={'koniec $date1', case={'gen',''}}, sl={function(date1, state) return 'pozno ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='slutet av $date1', uk={'кінець $date1', case={'gen',''}}, ur='اخیر $date1', vi='cuối năm $date1', ['zh-hans']='$date1晚期', ['zh-hant']='$date1晚期', default='<span style="color:red">late $date1</span>', }, ['circa'] = { nDates = 1, ar='حوالي $date1', ['be-tarask']={'каля $date1', case={'gen',''}}, bn='প্রায় $date1', ca='cap a $date1', cs='cca $date1', da='ca. $date1', de='etwa $date1', el='περ. $date1', en='circa $date1', ['en-gb']='circa $date1', ['en-ca']='circa $date1', eo='ĉirkaŭ $date1', es='hacia $date1', -- suggestion use "hacia"/"hacia el" or leave the latin circa or ca. et='ca. $date1', fa='حوالی $date1', -- template:Circa used 'حدود $date1' fi={function(date1, state) return 'suunnilleen ' .. decYear(state,'fi','ine') .. '$date1' end, case={'ine',''}}, -- template:Circa used 'noin $date1' fr=function(date1, state) if state.precision >= 11 then -- precision >= day return 'vers le $date1' else return 'vers $date1' end end, gl='entre $date1', -- template:Circa used 'cara a $date1' he='בסביבות $date1', hr='oko $date1', hsb={'wokoło $date1', case={'gen',''}}, hu='$date1 körül', it='$date1 circa', ja='$date1頃', ko='$date1경', mk='~ $date1', -- template:Circa used 'o. $date1' ml= 'ഉദ്ദേശം $date1', nb='ca. $date1', nn='ca. $date1', nds='üm un bi $date1', nl='ca. $date1', no='ca. $date1', pl={'około $date1', case={'gen',''}}, pt='cerca de $date1', qu='$date1chá', ro='circa $date1', ru={'около $date1', case={'gen',''}}, sk='cca $date1', sl={function(date1, state) return 'ok. ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='c:a $date1', -- template:Circa used 'ca $date1' uk={'близько $date1', case={'gen', ''}}, ur='قریباً $date1', vi='vào khoảng $date1', ['zh-hans']='约$date1', ['zh-hant']='約$date1', default='<span style="color:red">circa $date1</span>', }, ['uncertain'] = { nDates = 1, ['zh-hans']='$date1(?)', ['zh-hant']='$date1(?)', default='$date1 (?)', }, probably = { -- see Q56644435 nDates = 1, ast = 'probablemente $date1', bn = 'সম্ভবত $date1', ca = 'probablement $date1', cs = 'pravděpodobně $date1', de = 'wahrscheinlich $date1', el = 'πιθανόν $date1', en = 'probably $date1', ['en-gb']='probably $date1', ['en-ca']='probably $date1', eo = 'probable $date1', es = 'probable $date1', fa = 'احتمالاً $date1', fr = 'probablement $date1', he = 'כפי הנראה $date1', hu = 'feltehetőleg $date1', it = 'probabile $date1', mk = 'веројатно $date1', ml = 'ഒരുപക്ഷേ $date1', nl = 'waarschijnlijk $date1', pl = 'prawdopodobnie $date1', pt = 'provavelmente $date1', ru = 'вероятно $date1', sv = 'antagligen $date1', uk = 'ймовірно $date1', default='<span style="color:red">probably $date1</span>', }, possibly = { -- see Q30230067 nDates = 1, ast = 'posiblemente $date1', bn = 'যথাসম্ভব $date1', cs = 'možná $date1', de = 'möglicherweise $date1', en = 'possibly $date1', ['en-gb']='possibly $date1', ['en-ca']='possibly $date1', eo = 'eble $date1', el = 'πιθανόν $date1', fa = 'احتمالاً $date1', fr = 'peut-être $date1', hu = 'talán $date1', mk = 'можеби $date1', ml = 'മിക്കവാറും $date1', nl = 'mogelijk $date1', pl = 'być może $date1', pt = 'possivelmente $date1', ro = 'poate $date1', ru = 'возможно $date1', sv = 'möjligen $date1', uk = 'можливо $date1', default='<span style="color:red">possibly $date1</span>', }, presumably = { -- see Q18122778 nDates = 1, ast = 'presumiblemente $date1', de = 'vermutlich $date1', en = 'presumably $date1', ['en-gb']='presumably $date1', ['en-ca']='presumably $date1', fr = 'présumé $date1', it = 'presumibilmente $date1', ml = 'ഊഹപ്രകാരം $date1', ru = 'предположительно $date1', pl = 'przypuszczalnie $date1', uk = 'здогадно $date1', default='<span style="color:red">presumably $date1</span>', }, ['year unknown']= { nDates = 0, ca='Any desconegut', ['be-tarask']='Год невядомы', bn='অজানা বছর', cs='neznámý rok', de='Jahr unbekannt', el='άγνωστο έτος', en='year unknown', ['en-gb']='year unknown', ['en-ca']='year unknown', eo='jaro nesciata', es='Año desconocido', fa='سال نامعلوم', fi='tuntematon vuosi', fr='Année inconnue', he='שנה לא ידועה', hsb='lěto njeznate', hr='nepoznata godina', it='anno sconosciuto', ja='年代不明', la='Sine anno', ml='വർഷം ലഭ്യമല്ല', mk='непозната година', nb='ukjent år', nn='ukjent år', nl='zonder jaar', no='ukjent år', pl='rok nieznany', qu='Mana riqsisqa wata', ru='Год неизвестен', sk='neznámy rok', sl='neznano leto', sv='Okänt år', uk='рік невідомий', ur='سال نامعلوم', vi='không rõ năm', ['zh-hans']='年份未知', ['zh-hant']='年份未知', default='<span style="color:red">year unknown</span>', }, -- ========================================== -- === Parts of a whole ===================== -- ========================================== ['firsthalf'] = { nDates = 1, ar='النصف الأول من $date1', ['be-tarask']={'першая палова $date1', case={'gen',''}}, bn='$date1-এর প্রথমার্ধ', ca='1a meitat de $date1', cs={'1. polovina $date1', case={'gen',''}}, da='1. halvdel af $date1', de='1. Hälfte $date1', el='πρώτο μισό του $date1', en='first half of $date1', ['en-gb']='first half of $date1', ['en-ca']='first half of $date1', eo='unua duono de $date1', es='primera mitad de $date1', et='$date1 esimene pool', fa='نیمهٔ نخست $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 alkupuolisko' end, case={'gen',''}}, fr='1<sup>re</sup> moitié de $date1', gl='primeira metade de $date1', he='במחצית הראשונה של $date1', hsb={'prěnja połojca $date1', case={'gen',''}}, hu='$date1 első felében', hr='prva polovina $date1', it='prima metà del $date1', ja='$date1上半期', mk='прва половина на $date1', ml='$date1 ആദ്യപകുതി', nb='første halvdel av $date1', nn='fyrste halvdelen av $date1', nds='1. Helft $date1', nl='eerste helft $date1', no='første halvdel av $date1', pl={'pierwsza połowa $date1', case={'gen',''}}, pt='primeira metade de $date1', qu='$date1 ñawpaq kuskanpi', ro='prima jumătate a lui $date1', ru={'первая половина $date1', case={'gen',''}}, sk={'1. polovica $date1', case={'gen',''}}, sl={function(date1, state) return 'prva polovica ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='första halvan av $date1', uk={'перша половина $date1', case={'gen',''}}, ur='نصف اول $date1', vi='nửa đầu năm $date1', ['zh-hans']='$date1上半期', ['zh-hant']='$date1上半期', default='<span style="color:red">first half of $date1</span>', }, ['secondhalf'] = { nDates = 1, ar='النصف الثاني من $date1', ['be-tarask']={'другая палова $date1', case={'gen',''}}, bn='$date1-এর দ্বিতীয়ার্ধ', ca='2a meitat de $date1', cs={'2. polovina $date1', case={'gen',''}}, da='2. halvdel af $date1', de='2. Hälfte $date1', el='δεύτερο μισό του $date1', en='second half of $date1', ['en-gb']='second half of $date1', ['en-ca']='second half of $date1', eo='dua duono de $date1', es='segunda mitad de $date1', et='$date1 teine pool', fa='نیمهٔ دوم $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 loppupuolisko' end, case={'gen',''}}, fr='2<sup>e</sup> moitié de $date1', gl='segunda metade de $date1', he='במחצית השנייה של $date1', hsb={'druha połojca $date1', case={'gen',''}}, hr='druga polovina $date1', hu='$date1 második felében', it='seconda metà del $date1', ja='$date1下半期', mk='втора половина на $date1', ml='$date1 രണ്ടാം പകുതി', nb='andre halvdel av $date1', nn='andre halvdelen av $date1', nds='2. Helft vun $date1', nl='tweede helft $date1', no='andre halvdel av $date1', pl={'druga połowa $date1', case={'gen',''}}, pt='segunda metade de $date1', qu='$date1 qhipaq kuskanpi', ro='a doua jumătate a lui $date1', ru={'вторая половина $date1', case={'gen',''}}, sk={'2. polovica $date1', case={'gen',''}}, sl={'druga polovica {{IfNum | $date0 | leta}} $date1', case={'gen',''}}, sv='andra halvan av $date1', uk={'друга половина $date1', case={'gen',''}}, ur='نصف آخر $date1', vi='nửa cuối năm $date1', ['zh-hans']='$date1下半期', ['zh-hant']='$date1下半期', default='<span style="color:red">second half of $date1</span>', }, ['1quarter']= { nDates = 1, bn='$date1-এর প্রথম ত্রৈমাসিক', ['be-tarask']={'першая чвэрць $date1', case={'gen',''}}, cs={'1. čtvrtina $date1', case={'gen',''}}, de='erstes Viertel $date1', en='first quarter of $date1', ['en-gb']='first quarter of $date1', ['en-ca']='first quarter of $date1', eo='unua kvarono de $date1', es='primer cuarto del $date1', fa='ربع نخست $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 ensimmäinen neljännes' end, case={'gen',''}}, fr= function(date1, state) return 'premier quart '.. frOf[state.precision] ..' $date1' end, he='רבעון ראשון של $date1', hsb={'prěnja štwórćina $date1', case={'gen',''}}, hr='prva četvrtina $date1', it='primo quarto del $date1', ja='$date1第1四半期', ml='$date1 ഒന്നാം ത്രൈമാസം', nb='første kvartal av $date1', nl='eerste kwart $date1', nn='fyrste kvartalet av $date1', no='første kvartal av $date1', pl={'pierwsza ćwiartka $date1', case={'gen',''}}, qu='$date1 ñawpaq ñiqin tawa phatmanpi', ru={'первая четверть $date1', case={'gen',''}}, sk={'1. štvrtina $date1', case={'gen',''}}, sl={'prva četrtina {{IfNum | $date0 | leta}} $date1', case={'gen',''}}, sl={function(date1, state) return 'prva četrtina ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='första kvartalet av $date1', uk={'1-ша чверть $date1', case={'gen',''}}, ur='پہلی سہ ماہی $date1', vi='quý 1 năm $date1', ['zh-hans']='$date1第一季度', ['zh-hant']='$date1第一季度', default='<span style="color:red">first quarter of $date1</span>', }, ['2quarter']= { nDates = 1, ['be-tarask']={'другая чвэрць $date1', case={'gen',''}}, bn='$date1-এর দ্বিতীয় ত্রৈমাসিক', cs={'2. čtvrtina $date1', case={'gen',''}}, de='zweites Viertel $date1', en='second quarter of $date1', ['en-gb']='second quarter of $date1', ['en-ca']='second quarter of $date1', eo='dua kvarono de $date1', es='segundo cuarto del $date1', fa='ربع دوم $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 toinen neljännes' end, case={'gen',''}}, fr= function(date1, state) return 'deuxième quart '.. frOf[state.precision] ..' $date1' end, he='רבעון שני של $date1', hsb={'druha štwórćina $date1', case={'gen',''}}, hr='druga četvrtina $date1', it='secondo quarto del $date1', ja='$date1第2四半期', ml='$date1 രണ്ടാം ത്രൈമാസം', nb='andre kvartal av $date1', nl='tweede kwart $date1', nn='andre kvartalet av $date1', no='andre kvartal av $date1', pl={'druga ćwiartka $date1', case={'gen',''}}, qu='$date1 iskay ñiqin tawa phatmanpi', ru={'вторая четверть $date1', case={'gen',''}}, sk={'2. štvrtina $date1', case={'gen',''}}, sl={function(date1, state) return 'druga četrtina ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='andra kvartalet av $date1', uk={'2-га чверть $date1', case={'gen',''}}, ur='دوسری سہ ماہی $date1', vi='quý 2 năm $date1', ['zh-hans']='$date1第二季度', ['zh-hant']='$date1第二季度', default='<span style="color:red">second quarter of $date1</span>', }, ['3quarter']= { nDates = 1, ['be-tarask']={'трэцяя чвэрць $date1', case={'gen',''}}, bn='$date1-এর তৃতীয় ত্রৈমাসিক', cs={'3. čtvrtina $date1', case={'gen',''}}, de='drittes Viertel $date1', en='third quarter of $date1', ['en-gb']='third quarter of $date1', ['en-ca']='third quarter of $date1', eo='tria kvarono de $date1', es='tercer cuarto del $date1', fa='ربع سوم $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 kolmas neljännes' end, case={'gen',''}}, fr= function(date1, state) return 'troisième quart '.. frOf[state.precision] ..' $date1' end, he='רבעון שלישי של $date1', hsb={'třeća štwórćina $date1', case={'gen',''}}, hr='treća četvrtina $date1', it='terzo quarto del $date1', ja='$date1第3四半期', ml='$date1 മൂന്നാം ത്രൈമാസം', nb='tredje kvartal av $date1', nl='derde kwart $date1', nn='tredje kvartalet av $date1', no='tredje kvartal av $date1', pl={'trzecia ćwiartka $date1', case={'gen',''}}, qu='$date1 kimsa ñiqin tawa phatmanpi', sk={'3. štvrtina $date1', case={'gen',''}}, sl={function(date1, state) return 'tretja četrtina ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, ru={'третья четверть $date1', case={'gen',''}}, sv='tredje kvartalet av $date1', uk={'3-тя чверть $date1', case={'gen',''}}, ur='تیسری سہ ماہی $date1', vi='quý 3 năm $date1', ['zh-hans']='$date1第三季度', ['zh-hant']='$date1第三季度', default='<span style="color:red">third quarter of $date1</span>', }, ['4quarter']= { nDates = 1, ['be-tarask']={'чацьвёртая чвэрць $date1', case={'gen',''}}, bn='$date1-এর চতুর্থ ত্রৈমাসিক', cs={'4. čtvrtina $date1', case={'gen',''}}, de='letztes Viertel $date1', en='fourth quarter of $date1', ['en-gb']='fourth quarter of $date1', ['en-ca']='fourth quarter of $date1', eo='kvara kvarono de $date1', es='último cuarto del $date1', fa='ربع چهارم $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 viimeinen neljännes' end, case={'gen',''}}, fr= function(date1, state) return 'quatrième quart '.. frOf[state.precision] ..' $date1' end, he='רבעון רביעי של $date1', hsb={'poslednja štwórćina $date1', case={'gen',''}}, hr='četvrta četvrtina $date1', it='quarto quarto del $date1', ja='$date1第4四半期', ml='$date1 നാലാം ത്രൈമാസം', nb='fjerde kvartal av $date1', nl='vierde kwart $date1', nn='tredje kvartalet av $date1', no='fjerde kvartal av $date1', pl={'czwarta ćwiartka $date1', case={'gen',''}}, qu='$date1 qhipaq ñiqin tawa phatmanpi', ru={'последняя четверть $date1', case={'gen',''}}, sk={'4. štvrtina $date1', case={'gen',''}}, sl={function(date1, state) return 'zadnja četrtina ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='fjärde kvartalet av $date1', uk={'4-та чверть $date1', case={'gen',''}}, ur='چوتھی سہ ماہی $date1', vi='quý 4 năm $date1', ['zh-hans']='$date1第四季度', ['zh-hant']='$date1第四季度', default='<span style="color:red">forth quarter of $date1</span>', }, -- ========================================== -- === Seasons ============================== -- ========================================== ['spring'] = { nDates = 1, ar='ربيع $date1', ['be-tarask']='вясна $date1', bn='বসন্তকাল $date1', ca='primavera $date1', cs={'jaro $date1', case={'gen', ''}}, da='Forår $date1', de='Frühling $date1', en='Spring $date1', ['en-gb']='Spring $date1', ['en-ca']='Spring $date1', eo='printempo $date1', es='primavera de $date1', et='$date1 kevad', fa='بهار $date1', fi='Kevät $date1', fr='printemps $date1', gl='primavera $date1', he='אביב $date1', hr='proljeće $date1', hsb='nalěćo $date1', hu='$date1 tavasza', it='primavera $date1', ja='{{#if:$date1|$date1年}}春', mk='пролет $date1', ml='$date1 വസന്തം', nb='våren $date1', nds='Vörjohr $date1', nn='våren $date1', nl='voorjaar $date1', no='våren $date1', pl='wiosna $date1', pt='primavera de $date1', ro='primăvara lui $date1', ru='весна $date1', sk={'jar $date1', case={'gen', ''}}, sl='pomlad $date1', sv='våren $date1', uk='весна $date1', ur='بہار $date1', vi='mùa đông năm $date1', ['zh-hans']='{{#if:$date1|$date1年}}春季', ['zh-hant']='{{#if:$date1|$date1年}}春季', default='<span style="color:red">Spring $date1</span>', }, ['summer'] = { nDates = 1, ar='صيف $date1', ['be-tarask']='лета $date1', bn='গ্রীষ্মকাল $date1', ca='estiu $date1', cs={'léto $date1', case={'gen', ''}}, da='Sommer $date1', de='Sommer $date1', en='Summer $date1', ['en-gb']='Summer $date1', ['en-ca']='Summer $date1', eo='somero $date1', es='verano de $date1', et='$date1 suvi', fa='تابستان $date1', fi='Kesä $date1', fr='été $date1', gl='verán $date1', he='קיץ $date1', hr='ljeto $date1', hsb='lěćo $date1', hu='$date1 nyara', it='estate $date1', ja='{{#if:$date1|$date1年}}夏', mk='лето $date1', ml='$date1 വേനൽ', nb='sommeren $date1', nds='Sommer $date1', nn='sumaren $date1', nl='zomer $date1', no='sommeren $date1', pl='lato $date1', pt='verão de $date1', ro='vara lui $date1', ru='лето $date1', sk={'leto $date1', case={'gen', ''}}, sl='poletje $date1', sv='sommaren $date1', uk='літо $date1', ur='گرما $date1', vi='mùa hè năm $date1', ['zh-hans']='{{#if:$date1|$date1年}}夏季', ['zh-hant']='{{#if:$date1|$date1年}}夏季', default='<span style="color:red">Summer $date1</span>', }, ['autumn'] = { nDates = 1, ar='خريف $date1', ['be-tarask']='восень $date1', bn='শরত্কাল $date1', ca='tardor $date1', cs={'podzim $date1', case={'gen', ''}}, da='Efterår $date1', de='Herbst $date1', en='Autumn $date1', ['en-gb']='Autumn $date1', ['en-ca']='Autumn $date1', eo='aŭtuno $date1', es='otoño de $date1', et='$date1 sügis', fa='پاییز $date1', fi='Syksy $date1', fr='automne $date1', gl='outono $date1', he='סתיו $date1', hr='jesen $date1', hsb='nazyma $date1', hu='$date1 ősze', it='autunno $date1', ja='{{#if:$date1|$date1年}}秋', mk='есен $date1', ml='$date1 ശരത്കാലം', nb='høsten $date1', nds='Harvst $date1', nn='hausten $date1', nl='herfst $date1', no='høsten $date1', pl='jesień $date1', pt='outono de $date1', ro='toamna lui $date1', ru='осень $date1', sk={'jeseň $date1', case={'gen', ''}}, sl='jesen $date1', sv='hösten $date1', uk='осінь $date1', ur='خزاں $date1', vi='mùa thu năm $date1', ['zh-hans']='{{#if:$date1|$date1年}}秋季', ['zh-hant']='{{#if:$date1|$date1年}}秋季', default='<span style="color:red">Autumn $date1</span>', }, ['winter'] = { nDates = 1, ar='شتاء $date1', ['be-tarask']='зіма $date1', bn='শীতকাল $date1', ca='hivern $date1', cs={'zima $date1', case={'gen', ''}}, da='Vinter $date1', de='Winter $date1', en='Winter $date1', ['en-gb']='Winter $date1', ['en-ca']='Winter $date1', eo='vintro $date1', es='invierno de $date1', et='$date1 talv', fa='زمستان $date1', fi='Talvi $date1', fr='hiver $date1', gl='inverno $date1', he='חורף $date1', hr='zima $date1', hsb='zyma $date1', hu='$date1 tele', it='inverno $date1', ja='{{#if:$date1|$date1年}}冬', mk='зима $date1', ml='$date1 ശൈത്യകാലം', nds='Winter $date1', nl='winter $date1', nb='vinteren $date1', nn='vinteren $date1', no='vinteren $date1', pl='zima $date1', pt='inverno de $date1', ro='iarna lui $date1', ru='зима $date1', sk={'zima $date1', case={'gen', ''}}, sl='zima $date1', sv='vintern $date1', uk='зима $date1', ur='سرما $date1', vi='mùa đông năm $date1', ['zh-hans']='{{#if:$date1|$date1年}}冬季', ['zh-hant']='{{#if:$date1|$date1年}}冬季', default='<span style="color:red">Winter $date1</span>', }, -- ========================================== -- === Decade/Century/Millennium ============ -- ========================================== ['decade'] = { nDates = 1, ar='أعوام $date1', ['be-tarask']='$date1-я гады', bn='$date1-এর দশক', ca='dècada del $date1', cs=function(date1, state) return ((date1 % 100 == 0) and 'první dekáda' or ((date1 % 100) .. '. léta')) .. ' ' .. (math.floor(date1 / 100) + 1) .. '. století' end, da="$date1'erne", de='$date1er-Jahre', el='δεκαετία του $date1', en='$date1s', ['en-gb']='$date1s', ['en-ca']='$date1s', eo='$date1-aj jaroj', es='años $date1', et='$date1ndad', fa='دههٔ $date1', fi='$date1-luvulla', fr='années $date1', gl='anos $date1', he='שנות ה-$date1', hr='$date1-ih', hsb='$date1te lěta', hu='$date1-s évek', it="{{#ifexpr: $date1 mod 100 = 0 | primo decennio del {{Roman|{{#expr: ($date1 / 100) + 1}}}} sec. | anni '{{#expr: $date1 mod 100 }} del {{Roman|{{#expr: floor($date1 / 100) + 1}}}} sec.}}", ja='$date1年代', mk='$date1-ти', ml='ദശകം $date1-ൽ', nb='$date1-årene', nn='$date1-åra', nds='Johren $date1', nl='jaren $date1', no='$date1-årene', pl=function(date1, state) local str = 'lata '..(date1 % 100)..'. ' if (date1 % 100 == 0) then str = 'pierwsza dekada ' elseif (date1 % 100 == 10) then str = 'druga dekada ' end return str .. Roman(math.floor(date1 / 100) + 1) .. ' wieku' end, pt='década de $date1', qu='$date1 watakuna', ro='anii $date1', ru='$date1-е годы', sk=function(date1, state) return ((date1 % 100 == 0) and 'prvé desaťročie' or ((date1 % 100) .. '. roky')) .. ' ' .. (math.floor(date1 / 100) + 1) .. '. storočia' end, sl='$date1-ta leta', sv='$date1-talet{{#switch:$date1|1300|1400|1500|1600|1700|1800|1900|2000|2100=&nbsp;(decennium)}}', uk='$date1-ті роки', ur='دہائی $date1', vi='thập niên $date1', ['zh-hans']='$date1年代', ['zh-hant']='$date1年代', default='<span style="color:red">$date1s</span>', }, ['century'] = { nDates = 1, ar='القرن $date1', ['be-tarask']=function(date1, state) return Ordinal(date1, 'be-tarask') .. ' стагодзьдзе' end, bn=function(date1, state) return Ordinal(date1, 'bn') .. ' শতাব্দী' end, ca='segle {{small-caps|{{lc:{{Roman year|$date1}}}}}}', cs='$date1. století', da='$date1. århundrede', de='$date1. Jahrhundert', el='$date1ος αιώνας', en=function(date1, state) return Ordinal(date1, 'en') .. ' century' end, ['en-gb']=function(date1, state) return Ordinal(date1, 'en-gb') .. ' century' end, ['en-ca']=function(date1, state) return Ordinal(date1, 'en-ca') .. ' century' end, eo='$date1-a jarcento', es=function(date1, state) return 'siglo ' .. Roman(date1) end, et='$date1. sajand', fa='سدهٔ $date1', fi='{{#expr: ($date1 - 1) * 100}}-luvulla', fr=function(date1, state) return Ordinal(date1, 'fr', 'roman', 'm') .. ' siècle' end, gl=function(date1, state) return 'século ' .. Roman(date1) end, he='המאה ה-$date1', hr='$date1. stoljeće', hsb='$date1. lětstotk', hu='$date1. évszázad', it=function(date1, state) return Roman(date1) .. ' sec.' end, ja='$date1世紀', la='saeculum $date1', mk=function(date1, state) return Roman(date1) .. ' век' end, ml='$date1-ആം നൂറ്റാണ്ട്', nb=function(date1, state) return Ordinal(date1, 'nb') .. ' århundre' end, nn=function(date1, state) return 'det ' .. Ordinal(date1, 'no') .. ' århundre' end, nds='$date1. Johrhunnert', nl='$date1e eeuw', no=function(date1, state) return Ordinal(date1, 'no') .. ' århundre' end, pl=function(date1, state) return Roman(date1) .. ' ' .. decCentury('pl',state.case) end, pt=function(date1, state) return 'século ' .. Roman(date1) end, qu='$date1 ñiqin pachakwata', ro='secolul al $date1-lea', ru=function(date1, state) return Roman(date1) .. ' век' end, sk='$date1. storočie', sl=function(date1, state) return Ordinal(date1, 'sl') .. '. stoletje' end, sv='{{#expr:$date1 - 1}}00-talet', tt=function(date1, state) return Roman(date1) .. ' гасыр' end, uk=function(date1, state) return Roman(date1) .. ' ст.' end, ur='صدی $date1', vi='thế kỷ $date1', ['zh-hans']='$date1世纪', ['zh-hant']='$date1世紀', default='<span style="color:red">$date1 century</span>', }, ['millennium'] = { nDates = 1, ['be-tarask']=function(date1, state) return Ordinal(date1, 'be-tarask') .. ' тысячагодзьдзе' end, bn=function(date1, state) return Ordinal(date1, 'bn') .. ' সহস্রাব্দ' end, cs='$date1. tisíciletí', de='$date1. Jahrtausend', en=function(date1, state) return Ordinal(date1, 'en') .. ' millennium' end, ['en-gb']=function(date1, state) return Ordinal(date1, 'en-gb') .. ' millennium' end, ['en-ca']=function(date1, state) return Ordinal(date1, 'en-ca') .. ' millennium' end, eo='$date1-a jarmilo', es=function(date1, state) return Roman(date1) .. ' milenio' end, fa=function(date1, state) return 'هزارهٔ ' .. Ordinal(date1, 'fa') end, fr=function(date1, state) return Ordinal(date1, 'fr', 'roman', 'm') .. ' millénaire' end, hr='$date1. tisućljeće', hsb='$date1. lěttysac', it=function(date1, state) return Roman(date1) .. ' millennio' end, ja='$date1千年紀', la='millennium $date1', ml='$date1-മത്തെ സഹസ്രാബ്ദം', nb=function(date1, state) return Ordinal(date1, 'nb') .. ' årtusen' end, nn=function(date1, state) return Ordinal(date1, 'no') .. ' tusenår' end, nl='$date1e millennium', no=function(date1, state) return Ordinal(date1, 'no') .. ' årtusen' end, pl=function(date1, state) return Roman(date1) .. ' ' .. decMillennium('pl',state.case) end, pt=function(date1, state) return Roman(date1) .. ' milénio' end, qu='$date1 ñiqin waranqawata', ru=function(date1, state) return Roman(date1) .. ' тысячелетие' end, sk='$date1. tisícročie', sl=function(date1, state) return Ordinal(date1, 'sl') .. '. tisočletje' end, sv=function(date1, state) return Ordinal(date1, 'sv') .. ' årtusendet' end, uk=function(date1, state) return Roman(date1) .. ' тис.' end, ur=function(date1, state) return 'ہزارہ ' .. Ordinal(date1, 'ur') end, vi='thiên niên kỷ $date1', ['zh-hans']=function(date1, state) return Ordinal(date1, 'zh-hans') .. '千年' end, ['zh-hant']=function(date1, state) return Ordinal(date1, 'zh-hant') .. '千年' end, --default='{{Please translate|$date1 millennium|Module:I18n/complex date}}', default='<span style="color:red">$date1 millennium</span>', }, ['million'] = { de = '$date1 Millionen Jahre', en = '$date1 million years', ['en-gb'] = '$date1 million years', ['en-ca'] = '$date1 million years', eo = '$date1 milionoj da jaroj', es = '$date1 millones de años', nl = '$date1e miljoen jaar', fr = '$date1 millions d’années', it = '$date1 milioni di anni', ml = function(date1, state) if date1 >= 10 then return (date1 / 10) .. ' കോടി വർഷങ്ങൾ' -- 100 lakh = 1 crore else return (date1 * 10) .. ' ലക്ഷം വർഷങ്ങൾ' end end, nb = '$date1 millioner år', nl = '$date1 miljoen jaar', nn = '$date1 millionar år', pl = '$date1 milionów lat', pt = '$date1 milhões de anos', ru = '$date1 миллиона лет', uk = '$date1 мільйони років', ur = '$date1 ملین سال', ['zh-hans'] = '$date1百万年', ['zh-hant'] = '$date1百万年', default='<span style="color:red">$date1 million years</span>', nDates = 1, }, ['billion'] = { de = '$date1 Milliarden Jahre', en = '$date1 billion years', ['en-gb'] = '$date1 billion years', ['en-ca'] = '$date1 billion years', es = '$date1 mil millones de años', nl = '$date1e miljard jaar', fr = '$date1 milliard d’années', it = '$date1 miliardi di anni', ml = function(date1, state) return (date1 * 10) .. ' കോടി വർഷങ്ങൾ' end, nb = '$date1 milliarder år', nl = '$date1 miljard jaar', nn = '$date1 milliardar år', pl = '$date1 miliardów lat', pt = '$date1 mil milhões de anos', ru = '$date1 миллиардов лет', uk = '$date1 мільярдів років', ur = '$date1 بلین سال', ['zh-hans'] = '$date1十亿年', ['zh-hant'] = '$date1十億年', default='<span style="color:red$date1 billion years</span>', nDates = 1, }, -- ========================================== -- === Complex phrases ====================== -- ========================================== -- Phrases which for some languages can be build out of simpler phrases -- Their "default" state, used when no translation in the requested language was found, returns a "state" like -- table which is used to call the function again and built the phrase out of simple phrases -- $date1 - original date - 1 -- $date2 - original date -- if era is BC than $date1 and $date2 are swapped ['turn of the century'] = { nDates = 2, ['be-tarask']=function(date1, date2, state) return 'канец ' .. Roman(date1) ..' — пачатак ' .. Roman(date2) ..' стагодзьдзя' end, bn=function(date1, date2, state) return '$date1/' .. Ordinal(date2, 'bn') .. ' শতাব্দীর পালাবদলে' end, cs='přelom $date1. a $date2. století', de=function(date1, date2, state) return 'Jahrhundertwende $date1/' .. Ordinal(date2, 'de') end, en=function(date1, date2, state) return 'turn of the $date1/' .. Ordinal(date2, 'en') .. ' century' end, ['en-gb']=function(date1, date2, state) return 'turn of the $date1/' .. Ordinal(date2, 'en-gb') .. ' century' end, ['en-ca']=function(date1, date2, state) return 'turn of the $date1/' .. Ordinal(date2, 'en-ca') .. ' century' end, eo='ŝanĝo de la $date1-a al la $date2-a jarcentoj', es=function(date1, date2, state) return 'hacia finales del siglo ' .. Roman(date1) .. ' y principios del siglo ' .. Roman(date2) .. '' end, fi='{{#expr: $date1 * 100-100}}-luvun loppu ja {{#expr: $date2 * 100-100}}-luvun alku', fr=function(date1, date2, state) return 'fin du ' .. Ordinal(date1, 'fr', 'roman', 'm') .. ' siècle / début du ' .. Ordinal(date2, 'fr', 'roman', 'm') end, hr='kraj $date1. i početak $date2. stoljeća', hsb=function(date1, date2, state) return 'přechod ' .. Roman(date1) ..' do ' .. Roman(date2) ..' lětstotka' end, it=function(date1, date2, state) return 'fine del ' .. Roman(date1) .. ' sec. e inizio del ' .. Roman(date2) .. ' sec.' end, ja='$date1世紀から$date2世紀への変わり目', ml='$date1 നൂറ്റാണ്ട് അവസാനവും $date2 നൂറ്റാണ്ട് ആദ്യവും', nb=function(date1, date2, state) return 'slutten av det ' .. Ordinal(date1, 'nb') .. ' århundre/begynnelsen av det ' .. Ordinal(date2, 'nb') .. ' århundre' end, nl=function(date1, date2, state) return 'eind $date1e/begin $date2e eeuw' end, nn=function(date1, date2, state) return 'slutten av det ' .. Ordinal(date1, 'nn') .. ' hundreåret/byrjinga av det ' .. Ordinal(date2, 'nn') .. ' hundreåret' end, pl=function(date1, date2, state) return 'przełom ' .. Roman(date1) ..' i ' .. Roman(date2) ..' wieku' end, qu="$date1 ñiqin waranqawatamanta $date2 ñiqin waranqawataman t'ikrasqanpi", ru=function(date1, date2, state) return 'конец ' .. Roman(date1) ..' — начало ' .. Roman(date2) ..' века' end, sk='prelom $date1. a $date2. storočia', sl=function(date1, date2, state) return 'pozno ' .. Ordinal(date1, 'sl') .. '. in zgodnje ' .. Ordinal(date2, 'sl') .. '. stoletje' end, sv=function(date1, date2, state) return '$date1/' .. Ordinal(date2, 'sv') .. ' årtusendets sekelskifte' end, uk=function(date1, date2, state) return 'кін. ' .. Roman(date1) ..' ст. — поч. ' .. Roman(date2) ..' ст.' end, vi='bước sang thế kỷ $date2', ['zh-hans']=function(date1, date2, state) return '$date1世纪末' .. Ordinal(date2, 'zh-hans') .. '世纪初' end, ['zh-hant']=function(date1, date2, state) return '$date1世紀末' .. Ordinal(date2, 'zh-hant') .. '世紀初' end, default=function(date1, date2, state) return {conj='and', adj1 = 'late', unit1='century', era1=state.era1, adj2 = 'early', unit2='century', era2=state.era2} end, }, -- $date1 - original date - 10 -- $date2 - original date -- if era is BC than $date1 and $date2 are swapped ['turn of the decade']={ nDates = 2, ['be-tarask']='канец $date1-х — пачатак $date2-х гг.', bn='$date1-এর দশকের শেষে এবং $date2-এর দশকের শুরুতে', cs=function(date1, date2, state) local decade1 = math.floor((date1 % 100) / 10) local decade2 = math.floor((date2 % 100) / 10) local century1 = math.floor( date1 / 100) + 1 local century2 = math.floor( date2 / 100) + 1 if decade1 == 0 and date1 < date2 then return "přelom 1. a 2. dekády " .. century1 .. ". století" elseif decade2 == 0 and date2 < date1 then return "přelom 9. a 10. dekády " .. century1 .. ". století" elseif (decade1 == 9 and date1 < date2) or (decade2 == 9 and date2 < date1) then return "přelom " .. century1 .. ". a " .. century2 .. ". století" else return "přelom " .. decade1 .. "0. a " .. decade2 .. "0. let " .. century1 .. ". století" end end, de='späte $date1er und frühe $date2er', en='late $date1s and early $date2s', ['en-gb']='late $date1s and early $date2s', ['en-ca']='late $date1s and early $date2s', eo='ŝanĝo de la $date1-a al la $date2-a jardekoj', es='finales de los años $date1 y principios de los años $date2', fa='اواخر دههٔ $date1 و اوایل دههٔ $date2', fi='$date1-luvun loppu ja $date2-luvun alku', fr='fin des années $date1 / début des années $date2', hr='kraj $date1-ih i početak $date2-ih', hsb='pózdnje $date1te a zažne $date2te lěta', it='fine degli anni $date1 / inizio degli anni $date2', ja='$date1年代末から$date2年代初頭', ml='$date1കൾ അവസാനവും $date2കൾ ആദ്യവും', nb = 'sent på $date1-tallet og tidlig på $date2-tallet', nl = 'eind $date1e/begin $date2e decennium', nn = 'seint på $date1-talet og tidleg på $date2-talet', pl=function(date1, date2, state) local decade1 = math.floor(date1 % 100) local decade2 = math.floor(date2 % 100) local century1 = Roman(math.floor( date1 / 100) + 1) local century2 = Roman(math.floor( date2 / 100) + 1) if decade1 == 0 and date1 < date2 then return "przełom pierwszej i drugiej dekady " .. century1 .. " wieku" elseif decade1 == 10 and date1 < date2 then return "przełom drugiej dekady i lat 20. " .. century1 .. " wieku" elseif decade1 == 90 and decade2 == 0 then return "przełom ostatniej dekady " .. century1 .. " i pierwszej dekady " .. century2 .. " wieku" else return "przełom lat " .. decade1 .. ". i " .. decade2 .. ". " .. century1 .. " wieku" end end, qu='qhipaq $date1 watakuna ñawpaq $date2 watakunapas', ru='конец $date1-х — начало $date2-х гг.', sv='sena $date1-talet och tidiga $date2-talet', sk=function(date1, date2, state) local decade1 = math.floor((date1 % 100) / 10) local decade2 = math.floor((date2 % 100) / 10) local century1 = math.floor( date1 / 100) + 1 local century2 = math.floor( date2 / 100) + 1 if decade1 == 0 and date1 < date2 then return "prelom 1. a 2. desaťročia " .. century1 .. ". storočia" elseif decade2 == 0 and date2 < date1 then return "prelom 9. a 10. desaťročia " .. century1 .. ". storočia" elseif (decade1 == 9 and date1 < date2) or (decade2 == 9 and date2 < date1) then return "prelom " .. century1 .. ". a " .. century2 .. ". storočia" else return "prelom " .. decade1 .. "0. a " .. decade2 .. "0. rokov " .. century1 .. ". storočia" end end, sl='pozna $date1-ta in zgodnja $date2-ta leta', uk='кінець $date1-х — початок $date2-х рр.', ur='اخیر دہائی $date1 و ابتدا دہائی $date2', vi='bước sang thập niên $date2', ['zh-hans']='$date1年代末$date2年代初', ['zh-hant']='$date1年代末$date2年代初', default=function(date1, date2, state) return {conj='and', adj1 = 'late', unit1='decade', era1=state.era1, adj2 = 'early', unit2='decade', era2=state.era2} end, }, -- $date1 - original date - 1 -- $date2 - original date -- if era is BC than $date1 and $date2 are swapped ['turn of the year']={ nDates = 2, ['be-tarask']='канец $date1 — пачатак $date2 г.', bn='$date1-এর শেষে এবং $date2-এর শুরুতে', cs='přelom roku $date1/$date2', de='Jahreswechsel $date1/$date2', en='late $date1 and early $date2', ['en-gb']='late $date1 and early $date2', ['en-ca']='late $date1 and early $date2', eo='jarŝanĝo $date1 / $date2', es='finales de $date1 y principios de $date2', fa='اواخر $date1 و اوایل $date2', fi='vuoden $date1 loppu ja vuoden $date2 alku', fr='fin $date1 / début $date2', hsb='kónc $date1/spočatk $date2', ja='$date1年末から$date2年初め', ml='$date1 അവസാനവും $date2 ആദ്യവും', nb='årsskiftet $date1/$date2', nl='eind $date1 en begin $date2', nn='årsskiftet $date1/$date2', pl='koniec $date1 i początek $date2 roku', qu="$date1 qhipaq p'unchawkuna $date2 ñawpaq p'unchawkunapas", ru='конец $date1 — начало $date2 г.', sk='prelom rokov $date1/$date2', sl='konec leta $date1 in začetek $date2', sv='sena $date1 och tidiga $date2', uk='кінець $date1 — початок $date2 р.', ur='اخیر $date1 و ابتدا $date2', vi='bước sang năm $date2', ['zh-hans']='$date1年末$date2年初', ['zh-hant']='$date1年末$date2年初', default=function(date1, date2, state) return {conj='and', adj1 = 'late', unit1='year', era1=state.era1, adj2 = 'early', unit2='year', era2=state.era2} end, }, ['circa2'] = { nDates = 2, ar='حوالي $date1 – $date2', ['be-tarask']={'прыкладна з $date1 да прыкладна $date2', case={'gen','gen'}}, bn='প্রায় $date1 এবং প্রায় $date2-এর মাঝামাঝি', ca='cap a $date1 o $date2', cs='cca $date1 až $date2', da='ca. mellem $date1 og $date2', de='etwa $date1 bis $date2', el='περίπου μεταξύ $date1 και $date2', en='between circa $date1 and circa $date2', ['en-gb']='between circa $date1 and circa $date2', ['en-ca']='between circa $date1 and circa $date2', eo='proksimume de $date1 ĝis $date2', es='hacia $date1 o $date2', et='ca. $date1 ja $date2 vahel', fa='حدوداً بین $date1 و $date2', fi={function(date1, date2, state) return 'suunnilleen ' .. decYear(state,'fi','xxx') .. '$date1 ja $date2 välisenä aikana' end, case={'gen','gen'}}, fr='vers $date1-$date2', gl='entre $date1 e $date2', he='בסביבות ה- $date1 עד $date2', hr='oko između $date1 i $date2', hsb={'wot něhdźe $date1 do něhdźe $date2', case={'gen','gen'}}, hu='$date1 és $date2 között', it='tra il $date1 e il $date2 circa', ja='$date1頃と$date2頃の間', mk='~ помеѓу $date1 и $date2', ml='ഉദ്ദേശം $date1-നും ഉദ്ദേശം $date2-നും ഇടയ്ക്ക്', nb='ca. mellom $date1 og $date2', nn='ca. mellom $date1 og $date2', nds='üm un bi $date1 bet $date2', nl='ca. $date1 tot $date2', no='ca. mellom $date1 og $date2', pl={'od około $date1 do około $date2', case={'gen','gen'}}, pt='cerca de $date1 a $date2', qu="$date1 p'unchawmantachá $date2 p'unchawkamachá", ro='circa $date1 – $date2', ru={'с около $date1 до, примерно, $date2', case={'gen','gen'}}, sk='cca $date1 až $date2', sl={function(date1, date2, state) return 'ok. ' .. decYear(state,'sl','gen') .. ' $date1 do $date2' end, case={'gen','gen'}}, sv='c:a $date1 till $date2', uk={'з близько $date1 до, приблизно, $date2', case={'gen','gen'}}, ur='قریباً بین $date1 و $date2', vi='giữa vào khoảng $date1 và vào khoảng $date2', ['zh-hans']='约$date1~$date2间', ['zh-hant']='約$date1~$date2間', default=function(date1, date2, state) return {conj='between', adj1 = 'circa', unit1=state.unit1, era1=state.era1, adj2 = 'circa', unit2=state.unit2, era2=state.era2} end, }, -- ========================================== -- === Eras and calendars =================== -- ========================================== ['julian'] = { nDates = 2, ar='$date1 ($date2 حسب [[w:ar:التقويم اليولياني|تقويم يولياني]])', ['be-tarask']='$date1 ($date2 паводле [[w:be-tarask:Юліянскі каляндар|ст. ст.]]', bn='$date1 (জুলিয়ান ক্যালেন্ডারে $date2)', ca='$date1 ($date2 segons el [[w:ca:Calendari julià|calendari julià]])', cs='$date1 ($date2 podle [[w:cs:Juliánský kalendář|juliánského kalendáře]])', da='$date1 ($date2 [[w:da:Julianske kalender|Julianske kalender]])', de='$date1 ($date2 [[w:de:Julianischer Kalender|Julianischer Kalender]])', el='$date1 ($date2 με το [[w:el:Ιουλιανό ημερολόγιο|Ιουλιανό ημερολόγιο]])', en='$date1 ($date2 in [[w:Old Style and New Style dates|Julian calendar]])', ['en-gb']='$date1 ($date2 in [[w:Old Style and New Style dates|Julian calendar]])', ['en-ca']='$date1 ($date2 in [[w:Old Style and New Style dates|Julian calendar]])', fa='$date1 ($date2 در [[w:fa:Old Style and New Style dates|تقویم جولیان]])', eo='$date1 ($date2 laŭ la [[w:eo:Julia kalendaro|Julia Kalendaro]])', es='$date1 ($date2 según el [[w:es:Calendario juliano|calendario juliano]])', et='$date1 ($date2 [[w:et:Juliuse kalender|Juliuse kalendris]])', fi='$date1 ($date2 [[w:fi:juliaaninen kalenteri|juliaanisen kalenterin]] mukaan)', fr='$date1 ($date2 dans le [[w:fr:calendrier julien|calendrier julien]])', gl='$date1 ($date2 segundo o [[w:gl:Calendario xuliano|calendario xuliano]])', hu='$date1 ($date2 a [[w:hu:Julianus-naptár|Julián naptárban]])', hr='$date1 ($date2 prema [[w:hr:Julijanski kalendar|julijanskom kalendaru]])', he='$date1 ($date2 ב[[w:he:לוח היוליאני|הלוח היוליאני]])', hsb='$date1 ($date2 po julianiskej protyce)', it='$date1 ($date2 secondo il [[w:it:Calendario giuliano|calendario giuliano]])', ja='$date1([[w:ja:ユリウス暦|ユリウス暦]] $date2)', mk='$date1 ($date2 по [[w:mk:Јулијански календар|Јулијанскиот календар]])', ml='$date1 ([[w:ml:ജൂലിയൻ കാലഗണനാരീതി|ജൂലിയൻ വർഷത്തിൽ]] $date2)', ml='$date1 ([[w:Old Style and New Style dates|ജൂലിയൻ കാലഗണനാരീതിയിൽ]] $date2)', nds='$date1 ($date2 na’n [[w:nds:Juliaansch Klenner|Juliaanschen Klenner]])', nb='$date1 ($date2 etter [[w:no:Juliansk kalender|den julianske kalenderen]])', nn='$date1 ($date2 etter [[w:no:Juliansk kalender|den julianske kalenderen]])', nl='$date1 ($date2 volgens de [[w:nl:Juliaanse kalender|Juliaanse kalender]])', no='$date1 ($date2 etter [[w:no:Juliansk kalender|den julianske kalender]])', pl='$date1 ($date2 wedle [[w:pl:Daty nowego i starego porządku|kalendarza juliańskiego]])', pt='$date1 ($date2 no [[w:pt:Mudança para o calendário gregoriano|calendário juliano]])', qu='$date1 ($date2 [[w:qu:Hulyanu kalindaryu|Hulyanu kalindaryupi]])', ro='$date1 ($date2 în [[w:ro:calendarul iulian|calendarul iulian]])', ru='$date1 ($date2 [[w:ru:Юлианский календарь|по ст. ст.]])', sk='$date1 ($date2 podľa [[w:sk:Juliánsky kalendár|juliánskeho kalendára]])', sl='$date1 ($date2 po [[w:sl:Julijanski koledar|julijanskem koledarju]])', sv='$date1 ($date2 [[w:sv:Gamla stilen|gamla stilen]])', uk='$date1 ($date2 [[w:uk:Юліанський календар|Юліанський календар]])', ur='$date1 ($date2 حسب [[w:ur:نئے اور پانے انداز کی تاریخ|جولین تقویم]])', vi='$date1 ($date2 trong [[w:vi:Lịch Julius|lịch Juliêng]])', ['zh-hans']='$date1([[w:Old Style and New Style dates|格里历]]$date2)', ['zh-hant']='$date1([[w:Old Style and New Style dates|格里曆]]$date2)', default='<span style="color:red">$date1 ($date2 in [[w:Old Style and New Style dates|Julian calendar]])</span>', }, ['ad'] = { nDates = 1, ['be-tarask']='$date1 [[w:be-tarask:ад Нараджэньня Хрыста|пасьля Н. Х.]]', bn ='$date1 খ্রিস্টাব্দ', ca ='$date1 dC', cs ='$date1 n. l.', de ='$date1 n. Chr.', el ='$date1 μ.Χ.', en ='$date1 [[w:en:Anno Domini|AD]]', ['en-gb'] ='$date1 [[w:en:Anno Domini|AD]]', ['en-ca'] ='$date1 [[w:en:Anno Domini|AD]]', eo ='$date1 pK', es ='$date1 [[w:es:Anno Dómini|d. C.]]', fa ='$date1 م.', fi ='$date1 jaa.', fr ='$date1 ap. J.-C.', gl ='$date1 d.C.', he ='$date1 לספירה', hu ='i. sz. $date1', hr ='$date1 p. Kr.', hsb='$date1 po Chr.', it ='$date1 d.C.', ja ='西暦$date1', mk ='$date1 н.е.', ml ='ക്രി.വ. $date1', nb ='$date1 e.Kr.', nl ='$date1 n.Chr.', nn ='$date1 e.Kr.', no ='$date1 e.Kr.', pl ='$date1 [[w:pl:naszej ery|n.e.]]', ro ='$date1 d.Hr.', ru ='$date1 н. э.', sk ='$date1 po Kr.', sl ='$date1 n. št.', sv ='$date1 [[w:sv:Efter Kristus|e.Kr.]]', uk ='$date1 н. е.', ur ='$date1 ء', vi ='$date1 [[w:vi:Công Nguyên|CN]]', ['zh-hans'] ='[[w:zh:基督纪年|公元]]$date1', ['zh-hant'] ='[[w:zh:基督纪年|公元]]$date1', default='<span style="color:red">$date1 [[w:en:Anno Domini|AD]]</span>', }, ['ah'] = { nDates = 1, ['be-tarask']='$date1 [[w:be-tarask:Ісламскі каляндар|хіджры]]', bn ='$date1 [[w:bn:ইসলামি বর্ষপঞ্জি|হিজরী]]', cs ='$date1 [[w:cs:Anno Hegirae|AH]]', en ='$date1 [[w:en:Hijri year|AH]]', ['en-gb'] ='$date1 [[w:en:Hijri year|AH]]', ['en-ca'] ='$date1 [[w:en:Hijri year|AH]]', eo ='$date1 post la [[w:eo:Heĝiro|Heĝiro]]', fa ='$date1 ه.ق.', fr ='$date1 [[w:fr:Calendrier musulman|A.H.]]', he ="$date1 [[w:he:הלוח המוסלמי|להג'רה]]", hr ='$date1 god. hidžre', ja ='[[w:ja:ヒジュラ暦|ヒジュラ暦]]$date1', ml ='$date1 [[w:ml:ഇസ്‌ലാമിക കലണ്ടർ|ഹിജ്റ]]', nb ='$date1 [[w:no:Den muslimske kalenderen|A.H.]]', nn ='$date1 [[w:nn:Den muslimske kalenderen|A.H.]]', pl ='$date1 [[w:pl:Kalendarz muzułmański|A.H.]]', ru ='$date1 [[w:ru:Исламский календарь|хиджры]]', sk ='$date1 [[w:sk:A. H.|A. H.]]', sv ='$date1 [[w:sv:Muslimska kalendern|A.H.]]', uk ='$date1 [[w:uk:Мусульманський календар|хіджри]]', ur ='$date1 ھ', vi ='$date1 [[w:vi:Lịch Hồi giáo|AH]]', ['zh-hans'] ='[[w:en:Hijri year|Hijri历]]$date1', ['zh-hant'] ='[[w:en:Hijri year|Hijri曆]]$date1', default='<span style="color:red">$date1 [[w:en:Hijri year|AH]]</span>', }, ['bc'] = { nDates = 1, ar ='$date1 ق.م.', ['be-tarask']='$date1 [[w:be-tarask:да Нараджэньня Хрыста|да Н. Х.]]', bn ='খ্রিস্টপূর্ব $date1', ca ='$date1 [[w:ca:Abans de Crist|aC]]', cs ='$date1 [[w:cs:Př. n. l.|př. n. l.]]', de ='$date1 [[w:de:v. Chr.|v. Chr.]]', el ='$date1 π.Χ.', en ='$date1 [[w:en:Ante Christum Natum|BC]]', ['en-gb'] ='$date1 [[w:en:Ante Christum Natum|BC]]', ['en-ca'] ='$date1 [[w:en:Ante Christum Natum|BC]]', eo ='$date1 aK', es ='$date1 [[w:es:Antes de Cristo|a. C.]]', fa ='$date1 پ.م.', fi ='$date1 eaa.', fr ='$date1 av. J.-C.', gl ='$date1 [[w:gl:Antes de Cristo|a.C.]]', he ='$date1 לפני הספירה', hr ='$date1 [[w:hr:Pr. Kr.|pr. Kr.]]', hsb='$date1 do Chr.', hu ='i. e. $date1', it ='$date1 [[w:it:Avanti Cristo|a.C.]]', ja ='[[w:ja:紀元前|紀元前]]$date1', mk ='$date1 п.н.е.', ml ='ക്രി.മു. $date1', nds='$date1 vör uns Tied', nb ='$date1 f.Kr.', nl ='$date1 v.Chr.', nn ='$date1 f.Kr.', no ='$date1 f.Kr.', pl ='$date1 [[w:pl:przed naszą erą|p.n.e.]]', ro ='$date1 î.Hr.', ru ='$date1 до н. э.', sk ='$date1 [[w:sk:Pred Kristom|pred Kr.]]', sl ='$date1 pr. n. št.', sv ='$date1 [[w:sv:Före Kristus|f.Kr.]]', uk ='$date1 до н. е.', ur ='$date1 ق م', vi ='$date1 [[w:vi:Trước Công Nguyên|TCN]]', ['zh-hans'] ='[[w:zh:基督纪年|公元前]]$date1', ['zh-hant'] ='[[w:zh:基督纪年|公元前]]$date1', default='<span style="color:red">$date1 [[w:en:Ante Christum Natum|BC]]</span>', }, ['bp'] = { nDates = 1, ar ='$date1 [[w:ar:قبل الحاضر|قبل الحاضر]]', be ='$date1 [[w:be-tarask:Да нашых дзён|Да нашых дзён]]', ['be-tarask']='$date1 [[w:be-tarask:Да нашых дзён|Да нашых дзён]]', bn ='বর্তমানের আগে $date1', ca ='$date1 [[w:ca:BP (unitat de temps)|BP]]', cs ='$date1 [[w:cs:B. P.|B. P.]]', de ='$date1 [[w:de:Before Present|BP]]', el ='$date1 [[w:en:Before Present|BP]]', en ='$date1 [[w:en:Before Present|BP]]', ['en-gb'] ='$date1 [[w:en:Before Present|BP]]', ['en-ca'] ='$date1 [[w:en:Before Present|BP]]', eo='$date1 [[w:eo:Before Present|BP]]', es ='$date1 [[w:es:Before Present|BP]]', et ='$date1 [[w:et:Before Present|BP]]', fa ='$date1 پیش از اکنون', fi ='$date1 [[w:fi:BP (ajanlasku)|BP]]', fr ='$date1 [[w:fr:Avant le présent|avant le présent]]', hr ='$date1 [[w:hr:BP|prije današnjice]]', it ='$date1 [[w:it:Before Present|BP]]', ja ='$date1 [[w:ja:BP (年代測定)|BP]]', lv ='$date1 [[w:lv:Pirms mūsdienām|Pirms mūsdienām]]', mk ='$date1 [[w:mk:Пред сегашноста|п.с.]]', ml ='[[w:en:Before Present|ഇന്നത്തെ കാലത്തിനു മുമ്പ്]] $date1', nb ='$date1 [[w:no:Before Present|BP]]', nn ='$date1 [[w:nn:Before Present|BP]]', nl ='$date1 [[w:nl:Before Present|BP]]', no ='$date1 [[w:no:Before Present|BP]]', pl ='$date1 [[w:pl:Before Present|BP]]', pt ='$date1 [[w:pt:Antes do Presente|Antes do Presente]]', ru ='$date1 [[w:ru:До настоящего времени|до н. в.]]', sk ='$date1 [[w:sk:BP (čas)|BP]]', sl ='$date1 [[w:sl:pred sedanjostjo|p. s.]]', sr ='$date1 [[w:sr:Прије садашњости|Прије садашњости]]', sv ='$date1 [[w:sv:B.P.|B.P.]]', uk ='$date1 [[w:uk:Років тому|Років тому]]', ur ='$date1 قبل حاضر', vi ='$date1 [[w:en:Before Present|trước ngày nay]]', ['zh-hans'] ='[[w:zh:距今|距今]]$date1', ['zh-hant'] ='[[w:zh:距今|距今]]$date1', default='<span style="color:red">$date1 [[w:en:Before Present|BP]]</span>', }, } -- List of possible string inputs to "adj", "junction", "era", and "precision" fields -- Each term is matched to its synonym and the role: a - adjective, e - era -- p - precision, c - calendar and combos, r - certainty and j - conjunction p.Synonyms = { -- adjectives and prepositions ['quarter1']={'1quarter','a'}, ['1stquarter']={'1quarter','a'}, ['1st quarter']={'1quarter','a'}, ['first quarter']={'1quarter','a'}, ['1quarter']={'1quarter','a'}, ['quarter2']={'2quarter','a'}, ['2ndquarter']={'2quarter','a'}, ['2nd quarter']={'2quarter','a'}, ['second quarter']={'2quarter','a'}, ['2quarter']={'2quarter','a'}, ['quarter3']={'3quarter','a'}, ['3rdquarter']={'3quarter','a'}, ['3rd quarter']={'3quarter','a'}, ['third quarter']={'3quarter','a'}, ['3quarter']={'3quarter','a'}, ['quarter4']={'4quarter','a'}, ['4thquarter']={'4quarter','a'}, ['4th quarter']={'4quarter','a'}, ['fourth quarter']={'4quarter','a'}, ['4quarter']={'4quarter','a'}, ['spring']={'spring','a'}, ['summer']={'summer','a'}, ['fall']={'autumn','a'}, ['autumn']={'autumn','a'}, ['winter']={'winter','a'}, ['beginning']={'early','a'}, ['early']={'early','a'}, ['1half']={'firsthalf','a'}, ['1sthalf']={'firsthalf','a'}, ['1st half']={'firsthalf','a'}, ['firsthalf']={'firsthalf','a'}, ['first half']={'firsthalf','a'}, ['end']={'late','a'}, ['late']={'late','a'}, ['middle']={'mid','a'}, ['mid']={'mid','a'}, ['2half']={'secondhalf','a'}, ['2ndhalf']={'secondhalf','a'}, ['2nd half']={'secondhalf','a'}, ['secondhalf']={'secondhalf','a'}, ['second half']={'secondhalf','a'}, ['<']={'before','a'}, ['b']={'before','a'}, ['before']={'before','a'}, ['>']={'after','a'}, ['a']={'after','a'}, ['after']={'after','a'}, ['~']={'circa','a'}, ['c']={'circa','a'}, ['ca']={'circa','a'}, ['circa']={'circa','a'}, ['by']={'by','a'}, ['from']={'from','a'}, ['until']={'until','a'}, ['uncertain']={'uncertain','r'}, ['possibly']={'possibly','r'}, ['probably']={'probably','r'}, ['presumably']={'presumably','r'}, -- eras ['bc']={'bc','e'}, ['bce']={'bc','e'}, ['before present']={'bp','e'}, ['bp']={'bp','e'}, ['ce']={'ad','e'}, ['ad']={'ad','e'}, ['ah']={'ah','e'}, -- conjunctions ['&']={'and','j'}, ['and']={'and','j'}, ['or']={'or','j'}, ['-']={'from-until','j'}, ['–']={'from-until','j'}, ['from-until']={'from-until','j'}, ['between']={'between','j'}, ['circa2']={'circa2','a'}, -- precision ['day']={'day','p'}, ['month']={'month','p'}, ['year']={'year','p'}, ['s']={'decade','p'}, ['decade']={'decade','p'}, ['century']={'century','p'}, ['millennium']={'millennium','p'}, ['million']={'million','p'}, ['billion']={'billion','p'}, -- calendar ['julian']={'julian','c'}, ['islamic']={'islamic','c'}, ['hijri']={'islamic','c'}, -- "Turn of the ..." options are a combos which will be handled in similar way as calendars ['turn of the century']={'turn of the century','c'}, ['century turn']={'turn of the century','c'}, ['turn of the decade']={'turn of the decade','c'}, ['decade turn']={'turn of the decade','c'}, ['turn of the year']={'turn of the year','c'}, ['year turn']={'turn of the year','c'}, -- other dates which will be handled in similar way as calendars ['?']={'unknown','c'}, ['unknown']={'unknown','c'}, ['unknown year']={'year unknown','c'}, ['year unknown']={'year unknown','c'}, } return p fd1aqpq1a70t92o3nkj4hzkl8l48w6f 803609 803608 2019-02-26T00:19:07Z en>Pppery 0 Commons version of [[Module:Roman]] 803609 Scribunto text/plain --[[ __ __ _ _ ___ _ ___ __ _ _ _ | \/ | ___ __| |_ _| | ___ _|_ _/ |( _ ) _ __ / /__ ___ _ __ ___ _ __ | | _____ __ __| | __ _| |_ ___ | |\/| |/ _ \ / _` | | | | |/ _ (_)| || |/ _ \| '_ \ / / __/ _ \| '_ ` _ \| '_ \| |/ _ \ \/ / / _` |/ _` | __/ _ \ | | | | (_) | (_| | |_| | | __/_ | || | (_) | | | |/ / (_| (_) | | | | | | |_) | | __/> < | (_| | (_| | || __/ |_| |_|\___/ \__,_|\__,_|_|\___(_)___|_|\___/|_| |_/_/ \___\___/|_| |_| |_| .__/|_|\___/_/\_\ \__,_|\__,_|\__\___| |_| This code was copied from Commons please request changes there. Maintainers: *Jarekt ]] local function Ordinal(...) return require('Module:Ordinal-cd')._Ordinal(...) end local function Roman(...) return require('Module:Roman-cd')._Numeral(...) end -- ========================================================================== -- === Local Helper functions ============================================== -- ========================================================================== -- form of French "of" based on "precision", which is wikibase compatible date precision format: -- 6=millennium, 7=century, 8=decade, 9=year, 10=month, 11=day -- 1 2 3 4 5 mill cent deca year month day local frOf = {'du','du','du','du','du', 'du', 'du', 'de', 'de', 'de', 'du'} local function decYear(state,lang,case) --Declension of word "year" in several languages if state.precision == 9 then yearCase = { fi={xxx='vuosien', gen='vuoden', ill='vuoteen', ptv='vuotta', ela='vuodesta', ine='vuonna'}, sl={insP='letoma', ins='letom', gen='leta', loc='letu'}, hsb={insP='lětami', ins='lětom', gen='lěta', loc='lěće'} } return yearCase[lang][case] .. ' ' else return '' end end local function decCentury(lang,case) --Declension of word "century" in several languages centuryCase = { pl={nom='wiek', gen='wieku', ins='wiekiem', loc='wieku', default = 'wiek'}, hsb={nom='lětstotk', gen='lětstotka', ins='lětstotkom', loc='lětstotku', default = 'lětstotk'} } return centuryCase[lang][case] or centuryCase[lang]['default'] end local function decMillennium(lang,case) --Declension of word "century" in several languages millenniumCase = { pl={nom='tysiąclecie', gen='tysiąclecia', ins='tysiącleciem', loc='tysiącleciu', default = 'tysiąclecie'}, hsb={nom='lěttysac', gen='lěttysaca', ins='lěttysacom', loc='lěttysacu', default = 'lěttysac'} } return millenniumCase[lang][case] or millenniumCase[lang]['default'] end -- ========================================================================== -- === Output data structure =============================================== -- ========================================================================== local p = {} --[[ Multiple formats are possible: 1) string with key words "$date1" and "$date2" which will be replaced later by the date strings 2) function which can be used to create output string * Function INPUTS: - date1 and date2 string with date strings. "$date1" and "$date2" can also be used which will be replaced latter - state is a table with full description of the phrase we are trying to construct. It includes state.precision1 (and 2) which is wikibase compatible date precision format: 6=millennium, 7=century, 8=decade, 9=year, 10=month, 11=day 3) table with following fields: * table[1] is either string or a function * table.case is a 2 element table with Grammatical cases used by given language to alter month name or other adjective related to date # 1 and 2 ]] p.Translations = { -- ========================================== -- === Two date phrases ===================== -- ========================================== ['from-until'] = { nDates = 2, ar='من $date1 إلى $date2', ['be-tarask']={'з $date1 да $date2', case={'gen','gen'}}, bn='$date1 থেকে $date2 পর্যন্ত', ca='$date1 fins $date2', cs='$date1 až $date2', da='fra $date1 til $date2', de='$date1 bis $date2', el='από $date1 έως $date2', en='from $date1 until $date2', ['en-gb']='from $date1 until $date2', ['en-ca']='from $date1 until $date2', eo='de $date1 al $date2', es='de $date1 a $date2', et='$date1 kuni $date2', fa='از $date1 تا $date2', fi='$date1–$date2', -- välisenä aikana fr=function(date1, date2, state) -- could actually be better should be "d'octobre à mars 2013' not "de octore 2013 à mars 2013" if state.precision > 10 then -- precision > month return 'du $date1 au $date2' else -- "DE septebmbre" / "D'octobre" return require("Module:Linguistic").of(date1, 'fr') .. " à $date2" end end, gl='de $date1 a $date2', --Template:From until used 'desde $date1 ata $date2' he='מ- $date1 עד $date2', hr='od $date1 do $date2', hsb={'wot $date1 do $date2', case={'gen','gen'}}, hu='$date1 – $date2', it='dal $date1 al $date2', ja='$date1から$date2まで', mk='од $date1 до $date2', ml='$date1 മുതൽ $date2 വരെ', nb='fra $date1 til $date2', nds='$date1 bet $date2', nn='fra $date1 til $date2', nl='$date1 tot $date2', no='fra $date1 til $date2', pl={'od $date1 do $date2', case={'gen','gen'}}, ro='din $date1 până în $date2', pt='de $date1 a $date2', qu="$date1 p'unchawmanta $date2 p\'unchawkama", ru={'с $date1 по $date2', case={'gen','gen'}}, sk={'od $date1 do $date2', case={'gen','gen'}}, sl={'od $date1 do $date2', case={'gen','gen'}}, sv='från $date1 till $date2', uk={'з $date1 до $date2', case={'gen','gen'}}, ur='از $date1 تا $date2', vi='từ $date1 đến $date2', ['zh-hans']='从$date1到$date2', ['zh-hant']='從$date1到$date2', default='<span style="color:red">$date1 - $date2</span>', }, ['between']={ nDates = 2, ar='بين $date1 و $date2', ['be-tarask']={'між $date1 і $date2', case={'ins','ins'}}, bn='$date1 এবং $date2-এর মাঝামাঝি', ca='entre $date1 i $date2', cs={'mezi $date1 a $date2', case={'ins','ins'}}, da='mellem $date1 og $date2', de='zwischen $date1 und $date2', el='μεταξύ $date1 και $date2', en='between $date1 and $date2', ['en-gb']='between $date1 and $date2', ['en-ca']='between $date1 and $date2', eo='inter $date1 kaj $date2', es='entre $date1 y $date2', et='$date1 ja $date2 vahel', fa='بین $date1 و $date2', fi={function(date1, date2, state) return decYear(state,'fi','xxx') .. '$date1 ja $date2 välisenä aikana' end, case={'gen','gen'}}, fr='entre $date1 et $date2', gl='entre $date1 e $date2', he='בין $date1 ו- $date2', hr='između $date1 i $date2', hsb={'mjez $date1 a $date2', case={'ins','ins'}}, hu='$date1 és $date2 között', hi='$date1 और $date2 के बीच', it='tra il $date1 e il $date2', ja='$date1と$date2の間', mk='помеѓу $date1 и $date2', ml='$date1, $date2 എന്നിവയ്ക്കിടയ്ക്ക്', nb='mellom $date1 og $date2', nds='twischen $date1 un $date2', nn='mellom $date1 og $date2', nl='tussen $date1 en $date2', no='mellom $date1 og $date2', pl={'między $date1 a $date2', case={'ins','ins'}}, ro='între $date1 și $date2', pt='entre $date1 e $date2', qu="$date1 p'unchawpa $date2 p'unchawpapas chawpin", ru={'между $date1 и $date2', case={'ins','ins'}}, sk={'medzi $date1 a $date2', case={'ins','ins'}}, sl={function(date1, date2, state) return 'med ' .. decYear(state,'sl','insP') .. '$date1 in $date2' end, case={'ins','ins'}}, sv='mellan $date1 och $date2', uk={'між $date1 та $date2', case={'ins','ins'}}, ur='بین $date1 و $date2', vec='tra el $date1 e el $date2', vi='giữa $date1 và $date2', ['zh-hans']='在$date1到$date2之间', ['zh-hant']='在$date1到$date2之間', default='<span style="color:red">$date1 - $date2</span>', }, ['or'] = { nDates = 2, ar='$date1 أو $date2', ['be-tarask']='$date1 ці $date2', bn='$date1 অথবা $date2', ca='$date1 o $date2', cs='$date1 nebo $date2', da='$date1 eller $date2', de='$date1 oder $date2', el='$date1 ή $date2', en='$date1 or $date2', ['en-gb']='$date1 or $date2', ['en-ca']='$date1 or $date2', eo='$date1 aŭ $date2', es='$date1 o $date2', et='$date1 või $date2', fa='$date1 یا $date2', fi='$date1 tai $date2', fr='$date1 ou $date2', gl='$date1 ou $date2', he='$date1 או $date2', hr='$date1 ili $date2', hsb='$date1 abo $date2', hu='$date1 vagy $date2', it='$date1 o $date2', ja='$date1または$date2', mk='$date1 или $date2', ml='$date1 അഥവാ $date2', nb='$date1 eller $date2', nn='$date1 eller $date2', nds='$date1 oder $date2', nl='$date1 of $date2', no='$date1 eller $date2', pl='$date1 lub $date2', pt='$date1 ou $date2', qu='$date1 icha $date2', ro='$date1 sau $date2', ru='$date1 или $date2', sk='$date1 alebo $date2', sl='$date1 ali $date2', sv='$date1 eller $date2', uk='$date1 або $date2', ur='$date1 یا $date2', th='$date1 หรือ $date2', vi='$date1 hoặc $date2', ['zh-hans']='$date1或$date2', ['zh-hant']='$date1或$date2', default=function(date1, date2, state) return require("Module:linguistic").conj({date1, date2}, state.lang, 'or') end, }, ['and'] = { nDates = 2, ar='$date1 و $date2', ['be-tarask']='$date1 і $date2', bn='$date1 এবং $date2', ca='$date1 i $date2', cs='$date1 a $date2', da='$date1 og $date2', de='$date1 und $date2', el='$date1 και $date2', en='$date1 and $date2', ['en-gb']='$date1 and $date2', ['en-ca']='$date1 and $date2', eo='$date1 kaj $date2', es='$date1 y $date2', et='$date1 ja $date2', fa='$date1 و $date2', fi='$date1 ja $date2', fr='$date1 et $date2', gl='$date1 e $date2', he='$date1 ו- $date2', hu='$date1 és $date2', hsb='$date1 a $date2', it='$date1 e $date2', ja='$date1および$date2', mk='$date1 и $date2', ml='$date1 ഒപ്പം $date2', nb='$date1 og $date2', nn='$date1 og $date2', nds='$date1 un $date2', nl='$date1 en $date2', no='$date1 og $date2', pl='$date1 i $date2', pt='$date1 e $date2', qu='$date1wan $date2pas', ro='$date1 și $date2', ru='$date1 и $date2', sk='$date1 a $date2', sl='$date1 in $date2', sv='$date1 och $date2', uk='$date1 та $date2', ur='$date1 و $date2', vi='$date1 và $date2', ['zh-hans']='$date1和$date2', ['zh-hant']='$date1和$date2', default=function(date1, date2, state) return require("Module:linguistic").conj({date1, date2}, state.lang, 'and') end, }, -- ========================================== -- === Prepositions ========================= -- ========================================== ['by'] = { nDates = 1, ar='حوالي $date1', ['be-tarask']={'да $date1', case={'gen',''}}, bn='$date1 কর্তৃক', ca='vers $date1', cs={'kolem $date1', case={'gen',''}}, da='mod $date1', de='spätestens $date1', el='έως $date1', en='by $date1', ['en-gb']='by $date1', ['en-ca']='by $date1', eo='ne post $date1', es='para $date1', et='hiljemalt $date1', fa='توسط $date1', fi={function(date1, state) return decYear(state,'fi','ill') .. '$date1 mennessä' end, case={'ill',''}}, fr='$date1 (au plus tard)', gl='cara a $date1', he='עד $date1', hr='do $date1', hsb={'do $date1', case={'gen',''}}, it='entro il $date1', ja='$date1まで', mk='кон $date1', ml='$date1-ൽ', nb='innen $date1', nn='innan $date1', nds='$date1 oder vördem al', nl='tegen $date1', no='innen $date1', pl={'przed $date1', case={'ins',''}}, pt='por $date1', qu="$date1 p'unchawkama", ro='de $date1', ru={'к $date1', case={'dat',''}}, sk={'okolo $date1', case={'gen',''}}, sl={function(date1, state) return 'najpozneje ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='senast $date1', uk={'до $date1', case={'dat',''}}, ur='توسط $date1', vi='vào $date1', -- need to insert "đã" somewhere later in the sentence --> ['zh-hans']='$date1前后', ['zh-hant']='$date1前后', default='<span style="color:red">by $date1</span>', }, ['before'] = { nDates = 1, ar='قبل $date1', ['be-tarask']={'перад $date1', case={'ins',''}}, bn='$date1-এর আগে', bg='Преди $date1', ca='abans $date1', cs={'před $date1', case={'ins',''}}, da='før $date1', de='vor $date1', el='πριν από $date1', en='before $date1', ['en-gb']='before $date1', ['en-ca']='before $date1', eo='antaŭ $date1', es='antes de $date1', et='enne $date1', fa='پیش از $date1', fi={function(date1, state) return 'ennen ' .. decYear(state,'fi','ptv') .. '$date1' end, case={'ptv',''}}, fr='avant $date1', gl='antes do $date1', he='לפני $date1', hr='prije $date1', hsb={'do $date1', case={'gen',''}}, hu='$date1 előtt', it='prima del $date1', ja='$date1より前', mk='пред $date1', ml='$date1-നു മുമ്പ്', nb='før $date1', nn='før $date1', nds='vör $date1', nl='voor $date1', no='før $date1', pl={'przed $date1', case={'ins',''}}, pt='antes de $date1', qu="$date1 p'unchaw ñawpan", ro='înainte de $date1', ru={'ранее $date1', case={'gen',''}}, sk={'pred $date1', case={'ins',''}}, sl={function(date1, state) return 'pred ' .. decYear(state,'sl','ins') .. ' $date1' end, case={'ins',''}}, sv='före $date1', uk={'до $date1', case={'gen',''}}, ur='قبل $date1', vi='trước $date1', ['zh-hans']='在$date1之前', ['zh-hant']='在$date1之前', default='<span style="color:red">before $date1</span>', }, ['from'] = { nDates = 1, ar='من $date1', ['be-tarask']={'з $date1', case={'gen',''}}, bn='$date1 থেকে', cs={'od $date1', case={'gen',''}}, de='Seit $date1', el='από $date1', en='from $date1', ['en-gb']='from $date1', ['en-ca']='from $date1', eo='ekde $date1', es='desde $date1', et='alates $date1', fa='از $date1', fi={function(date1, state) return decYear(state,'fi','ela') .. '$date1 alkaen' end, case={'ela',''}}, fr= function(date1, state) return 'à partir '.. frOf[state.precision] ..' $date1' end, gl='desde o $date1', he='מ- $date1', hr='od $date1', hsb={'wot $date1', case={'gen',''}}, it='dal $date1', ja='$date1から', nds='vonaf $date1', mk='од $date1', nb='fra $date1', nn='frå $date1', ml='$date1 മുതൽ', nl='vanaf $date1', no='fra $date1', pl={'od $date1', case={'gen',''}}, qu="$date1 p'unchawmanta", ro='din $date1', ru={'С $date1', case={'gen',''}}, sk={'od $date1', case={'gen',''}}, sl={function(date1, state) return 'od ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='från $date1', uk={'з $date1', case={'gen',''}}, ur='از $date1', vi='từ $date1', ['zh-hans']='自$date1', ['zh-hant']='自$date1', default='<span style="color:red">from $date1</span>', }, ['until'] = { nDates = 1, ['be-tarask']={'усутыч да $date1', case={'gen',''}}, bn='$date1 পর্যন্ত', cs={'do $date1', case={'gen', ''}}, de='bis $date1', en='until $date1', ['en-gb']='until $date1', ['en-ca']='until $date1', eo='ĝis $date1', es='hasta $date1', fr=function(date1, state) if state.precision > 10 then -- precision > month return "jusqu'au $date1" else return "jusqu'à $date1" end end, hr='do $date1', hsb={'do $date1', case={'gen',''}}, it='al $date1', nb='til $date1', nl='tot $date1', nn='til $date1', ml='$date1 വരെ', pl={'do $date1', case={'gen',''}}, ru={'вплоть до $date1', case={'gen',''}}, sk={'do $date1', case={'gen', ''}}, uk={'аж до $date1', case={'gen',''}}, ur='تک $date1', ['zh-hans']='直到$date1', ['zh-hant']='直到$date1', default='<span style="color:red">until $date1</span>', }, ['after'] = { nDates = 1, ar='بعد $date1', ['be-tarask']={'пасьля $date1', case={'gen',''}}, bn='$date1-এর পরে', ca='després de $date1', cs={'po $date1', case={'loc',''}}, de='nach $date1', da='efter $date1', el='μετά από $date1', en='after $date1', ['en-gb']='after $date1', ['en-ca']='after $date1', eo='post $date1', es='después de $date1', et='pärast $date1', fa='پس از $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 jälkeen' end, case={'gen',''}}, fr='après $date1', gl='despois do $date1', he='לאחר $date1', hr='nakon $date1', hu='$date1 után', hsb={'po $date1', case={'loc',''}}, it='dopo il $date1', ja='$date1より後', mk='по $date1', ml='$date1-നു ശേഷം', nb='etter $date1', nn='etter $date1', nds='na $date1', nl='na $date1', no='etter $date1', pl={'po $date1', case={'loc',''}}, pt='após $date1', qu="$date1 p'unchaw qhipan", ro='după $date1', ru={'после $date1', case={'gen',''}}, sk={'po $date1', case={'loc',''}}, sl={function(date1, state) return 'po ' .. decYear(state,'sl','loc') .. ' $date1' end, case={'loc',''}}, sv='efter $date1', uk={'після $date1', case={'gen',''}}, ur='بعد $date1', vi='sau $date1', ['zh-hans']='在$date1之后', ['zh-hant']='在$date1之後', default='<span style="color:red">after $date1</span>', }, -- ========================================== -- === Adjectives =========================== -- ========================================== ['early'] = { nDates = 1, ar='بداية $date1', ['be-tarask']={'пачатак $date1', case={'gen',''}}, bn='$date1-এর শুরুতে', ca='al principi de $date1', cs={'začátek $date1', case={'gen',''}}, da='tidlig $date1', de='Anfang $date1', el='αρχές του $date1', en='early $date1', ['en-gb']='early $date1', ['en-ca']='early $date1', eo='komence de $date1', es='a principios de $date1', et='$date1 algus', fa='اوایل $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 alku' end, case={'gen',''}}, fr='début $date1', gl='a principios de $date1', he='תחילת $date1', hr='početak $date1', hsb={'spočatk $date1', case={'gen',''}}, hu='$date1 elején', it='inizio del $date1', ja='$date1初頭', mk='почеток на $date1', ml='$date1 ആദ്യപാദം', nb='tidlig $date1', nn='tidleg $date1', nds='Anfang $date1', nl='begin $date1', no='tidlig $date1', pl={'początek $date1', case={'gen',''}}, pt='a princípios de $date1', qu="$date1 ñawpaq p'unchawkunapi", ro='începând cu $date1', ru={'начало $date1', case={'gen',''}}, sk={'začiatok $date1', case={'gen',''}}, sl=function(date1, state)-- could actually be better should be "d'octobre à mars 2013' not "de octore 2013 à mars 2013" if state.precision == 9 then -- precision > month return 'zgodnje leto $date1' else -- "DE septebmbre" / "D'octobre" return 'zgodnji $date1' end end, sv='början av $date1', uk={'початок $date1', case={'gen',''}}, ur='ابتدا $date1', vi='đầu năm $date1', ['zh-hans']='$date1早期', ['zh-hant']='$date1早期', default='<span style="color:red">early $date1</span>', }, ['mid'] = { nDates = 1, ar='منتصف $date1', ['be-tarask']={'сярэдзіна $date1', case={'gen',''}}, bn='$date1-এর মাঝামাঝি', ca='a mitjan $date1', cs={'polovina $date1', case={'gen',''}}, da='midten af $date1', de='Mitte $date1', el='μέσα του $date1', en='mid $date1', ['en-gb']='mid $date1', ['en-ca']='mid $date1', eo='meze de $date1', es='a mediados de $date1', et='$date1 keskel', fa='اواسط $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 puoliväli' end, case={'gen',''}}, fr='mi $date1', gl='a mediados de $date1', he='אמצע $date1', hr='sredina $date1', hsb={'srjedź $date1', case={'gen',''}}, hu='$date1 közepén', it='metà del $date1', ja='$date1半ば', mk='средина на $date1', ml='$date1 മദ്ധ്യം', nb='midten av $date1', nn='midten av $date1', nds='Midd $date1', nl='midden $date1', no='midten av $date1', pl={'środek $date1', case={'gen',''}}, pt='meados de $date1', qu="$date1 chawpin p'unchawkunapi", ro='mijlocul lui $date1', ru={'середина $date1', case={'gen',''}}, sk={'polovica $date1', case={'gen',''}}, sl={function(date1, state) return 'sredina ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='mitten av $date1', uk={'середина $date1', case={'gen',''}}, ur='وسط $date1', vi='giữa năm $date1', ['zh-hans']='$date1中期', ['zh-hant']='$date1中期', default='<span style="color:red">mid $date1</span>', }, ['late'] = { nDates = 1, ar='نهاية $date1', bn='$date1-এর শেষে', ['be-tarask']={'канец $date1', case={'gen',''}}, ca='al final de $date1', cs='konec $date1', da='sen $date1', de='Ende $date1', el='τέλη του $date1', en='late $date1', ['en-gb']='late $date1', ['en-ca']='late $date1', eo='fine de $date1', es='a fines de $date1', et='$date1 lõpp', fa='اواخر $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 loppu' end, case={'gen',''}}, fr='fin $date1', gl='a finais de $date1', he='סוף $date1', hr='kraj $date1', hsb={'kónc $date1', case={'gen',''}}, hu='$date1 végén', it='fine del $date1', ja='$date1末', mk='крајот на $date1', ml='$date1 അന്ത്യപാദം', nb='sent $date1', no='seint $date1', nds='Enn $date1', nl='eind $date1', no='sent $date1', pl={'koniec $date1', case={'gen',''}}, pt='ao final de $date1', qu="$date1 qhipaq p'unchawkunapi", ro='sfârșitul lui $date1', ru={'конец $date1', case={'gen',''}}, sk={'koniec $date1', case={'gen',''}}, sl={function(date1, state) return 'pozno ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='slutet av $date1', uk={'кінець $date1', case={'gen',''}}, ur='اخیر $date1', vi='cuối năm $date1', ['zh-hans']='$date1晚期', ['zh-hant']='$date1晚期', default='<span style="color:red">late $date1</span>', }, ['circa'] = { nDates = 1, ar='حوالي $date1', ['be-tarask']={'каля $date1', case={'gen',''}}, bn='প্রায় $date1', ca='cap a $date1', cs='cca $date1', da='ca. $date1', de='etwa $date1', el='περ. $date1', en='circa $date1', ['en-gb']='circa $date1', ['en-ca']='circa $date1', eo='ĉirkaŭ $date1', es='hacia $date1', -- suggestion use "hacia"/"hacia el" or leave the latin circa or ca. et='ca. $date1', fa='حوالی $date1', -- template:Circa used 'حدود $date1' fi={function(date1, state) return 'suunnilleen ' .. decYear(state,'fi','ine') .. '$date1' end, case={'ine',''}}, -- template:Circa used 'noin $date1' fr=function(date1, state) if state.precision >= 11 then -- precision >= day return 'vers le $date1' else return 'vers $date1' end end, gl='entre $date1', -- template:Circa used 'cara a $date1' he='בסביבות $date1', hr='oko $date1', hsb={'wokoło $date1', case={'gen',''}}, hu='$date1 körül', it='$date1 circa', ja='$date1頃', ko='$date1경', mk='~ $date1', -- template:Circa used 'o. $date1' ml= 'ഉദ്ദേശം $date1', nb='ca. $date1', nn='ca. $date1', nds='üm un bi $date1', nl='ca. $date1', no='ca. $date1', pl={'około $date1', case={'gen',''}}, pt='cerca de $date1', qu='$date1chá', ro='circa $date1', ru={'около $date1', case={'gen',''}}, sk='cca $date1', sl={function(date1, state) return 'ok. ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='c:a $date1', -- template:Circa used 'ca $date1' uk={'близько $date1', case={'gen', ''}}, ur='قریباً $date1', vi='vào khoảng $date1', ['zh-hans']='约$date1', ['zh-hant']='約$date1', default='<span style="color:red">circa $date1</span>', }, ['uncertain'] = { nDates = 1, ['zh-hans']='$date1(?)', ['zh-hant']='$date1(?)', default='$date1 (?)', }, probably = { -- see Q56644435 nDates = 1, ast = 'probablemente $date1', bn = 'সম্ভবত $date1', ca = 'probablement $date1', cs = 'pravděpodobně $date1', de = 'wahrscheinlich $date1', el = 'πιθανόν $date1', en = 'probably $date1', ['en-gb']='probably $date1', ['en-ca']='probably $date1', eo = 'probable $date1', es = 'probable $date1', fa = 'احتمالاً $date1', fr = 'probablement $date1', he = 'כפי הנראה $date1', hu = 'feltehetőleg $date1', it = 'probabile $date1', mk = 'веројатно $date1', ml = 'ഒരുപക്ഷേ $date1', nl = 'waarschijnlijk $date1', pl = 'prawdopodobnie $date1', pt = 'provavelmente $date1', ru = 'вероятно $date1', sv = 'antagligen $date1', uk = 'ймовірно $date1', default='<span style="color:red">probably $date1</span>', }, possibly = { -- see Q30230067 nDates = 1, ast = 'posiblemente $date1', bn = 'যথাসম্ভব $date1', cs = 'možná $date1', de = 'möglicherweise $date1', en = 'possibly $date1', ['en-gb']='possibly $date1', ['en-ca']='possibly $date1', eo = 'eble $date1', el = 'πιθανόν $date1', fa = 'احتمالاً $date1', fr = 'peut-être $date1', hu = 'talán $date1', mk = 'можеби $date1', ml = 'മിക്കവാറും $date1', nl = 'mogelijk $date1', pl = 'być może $date1', pt = 'possivelmente $date1', ro = 'poate $date1', ru = 'возможно $date1', sv = 'möjligen $date1', uk = 'можливо $date1', default='<span style="color:red">possibly $date1</span>', }, presumably = { -- see Q18122778 nDates = 1, ast = 'presumiblemente $date1', de = 'vermutlich $date1', en = 'presumably $date1', ['en-gb']='presumably $date1', ['en-ca']='presumably $date1', fr = 'présumé $date1', it = 'presumibilmente $date1', ml = 'ഊഹപ്രകാരം $date1', ru = 'предположительно $date1', pl = 'przypuszczalnie $date1', uk = 'здогадно $date1', default='<span style="color:red">presumably $date1</span>', }, ['year unknown']= { nDates = 0, ca='Any desconegut', ['be-tarask']='Год невядомы', bn='অজানা বছর', cs='neznámý rok', de='Jahr unbekannt', el='άγνωστο έτος', en='year unknown', ['en-gb']='year unknown', ['en-ca']='year unknown', eo='jaro nesciata', es='Año desconocido', fa='سال نامعلوم', fi='tuntematon vuosi', fr='Année inconnue', he='שנה לא ידועה', hsb='lěto njeznate', hr='nepoznata godina', it='anno sconosciuto', ja='年代不明', la='Sine anno', ml='വർഷം ലഭ്യമല്ല', mk='непозната година', nb='ukjent år', nn='ukjent år', nl='zonder jaar', no='ukjent år', pl='rok nieznany', qu='Mana riqsisqa wata', ru='Год неизвестен', sk='neznámy rok', sl='neznano leto', sv='Okänt år', uk='рік невідомий', ur='سال نامعلوم', vi='không rõ năm', ['zh-hans']='年份未知', ['zh-hant']='年份未知', default='<span style="color:red">year unknown</span>', }, -- ========================================== -- === Parts of a whole ===================== -- ========================================== ['firsthalf'] = { nDates = 1, ar='النصف الأول من $date1', ['be-tarask']={'першая палова $date1', case={'gen',''}}, bn='$date1-এর প্রথমার্ধ', ca='1a meitat de $date1', cs={'1. polovina $date1', case={'gen',''}}, da='1. halvdel af $date1', de='1. Hälfte $date1', el='πρώτο μισό του $date1', en='first half of $date1', ['en-gb']='first half of $date1', ['en-ca']='first half of $date1', eo='unua duono de $date1', es='primera mitad de $date1', et='$date1 esimene pool', fa='نیمهٔ نخست $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 alkupuolisko' end, case={'gen',''}}, fr='1<sup>re</sup> moitié de $date1', gl='primeira metade de $date1', he='במחצית הראשונה של $date1', hsb={'prěnja połojca $date1', case={'gen',''}}, hu='$date1 első felében', hr='prva polovina $date1', it='prima metà del $date1', ja='$date1上半期', mk='прва половина на $date1', ml='$date1 ആദ്യപകുതി', nb='første halvdel av $date1', nn='fyrste halvdelen av $date1', nds='1. Helft $date1', nl='eerste helft $date1', no='første halvdel av $date1', pl={'pierwsza połowa $date1', case={'gen',''}}, pt='primeira metade de $date1', qu='$date1 ñawpaq kuskanpi', ro='prima jumătate a lui $date1', ru={'первая половина $date1', case={'gen',''}}, sk={'1. polovica $date1', case={'gen',''}}, sl={function(date1, state) return 'prva polovica ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='första halvan av $date1', uk={'перша половина $date1', case={'gen',''}}, ur='نصف اول $date1', vi='nửa đầu năm $date1', ['zh-hans']='$date1上半期', ['zh-hant']='$date1上半期', default='<span style="color:red">first half of $date1</span>', }, ['secondhalf'] = { nDates = 1, ar='النصف الثاني من $date1', ['be-tarask']={'другая палова $date1', case={'gen',''}}, bn='$date1-এর দ্বিতীয়ার্ধ', ca='2a meitat de $date1', cs={'2. polovina $date1', case={'gen',''}}, da='2. halvdel af $date1', de='2. Hälfte $date1', el='δεύτερο μισό του $date1', en='second half of $date1', ['en-gb']='second half of $date1', ['en-ca']='second half of $date1', eo='dua duono de $date1', es='segunda mitad de $date1', et='$date1 teine pool', fa='نیمهٔ دوم $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 loppupuolisko' end, case={'gen',''}}, fr='2<sup>e</sup> moitié de $date1', gl='segunda metade de $date1', he='במחצית השנייה של $date1', hsb={'druha połojca $date1', case={'gen',''}}, hr='druga polovina $date1', hu='$date1 második felében', it='seconda metà del $date1', ja='$date1下半期', mk='втора половина на $date1', ml='$date1 രണ്ടാം പകുതി', nb='andre halvdel av $date1', nn='andre halvdelen av $date1', nds='2. Helft vun $date1', nl='tweede helft $date1', no='andre halvdel av $date1', pl={'druga połowa $date1', case={'gen',''}}, pt='segunda metade de $date1', qu='$date1 qhipaq kuskanpi', ro='a doua jumătate a lui $date1', ru={'вторая половина $date1', case={'gen',''}}, sk={'2. polovica $date1', case={'gen',''}}, sl={'druga polovica {{IfNum | $date0 | leta}} $date1', case={'gen',''}}, sv='andra halvan av $date1', uk={'друга половина $date1', case={'gen',''}}, ur='نصف آخر $date1', vi='nửa cuối năm $date1', ['zh-hans']='$date1下半期', ['zh-hant']='$date1下半期', default='<span style="color:red">second half of $date1</span>', }, ['1quarter']= { nDates = 1, bn='$date1-এর প্রথম ত্রৈমাসিক', ['be-tarask']={'першая чвэрць $date1', case={'gen',''}}, cs={'1. čtvrtina $date1', case={'gen',''}}, de='erstes Viertel $date1', en='first quarter of $date1', ['en-gb']='first quarter of $date1', ['en-ca']='first quarter of $date1', eo='unua kvarono de $date1', es='primer cuarto del $date1', fa='ربع نخست $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 ensimmäinen neljännes' end, case={'gen',''}}, fr= function(date1, state) return 'premier quart '.. frOf[state.precision] ..' $date1' end, he='רבעון ראשון של $date1', hsb={'prěnja štwórćina $date1', case={'gen',''}}, hr='prva četvrtina $date1', it='primo quarto del $date1', ja='$date1第1四半期', ml='$date1 ഒന്നാം ത്രൈമാസം', nb='første kvartal av $date1', nl='eerste kwart $date1', nn='fyrste kvartalet av $date1', no='første kvartal av $date1', pl={'pierwsza ćwiartka $date1', case={'gen',''}}, qu='$date1 ñawpaq ñiqin tawa phatmanpi', ru={'первая четверть $date1', case={'gen',''}}, sk={'1. štvrtina $date1', case={'gen',''}}, sl={'prva četrtina {{IfNum | $date0 | leta}} $date1', case={'gen',''}}, sl={function(date1, state) return 'prva četrtina ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='första kvartalet av $date1', uk={'1-ша чверть $date1', case={'gen',''}}, ur='پہلی سہ ماہی $date1', vi='quý 1 năm $date1', ['zh-hans']='$date1第一季度', ['zh-hant']='$date1第一季度', default='<span style="color:red">first quarter of $date1</span>', }, ['2quarter']= { nDates = 1, ['be-tarask']={'другая чвэрць $date1', case={'gen',''}}, bn='$date1-এর দ্বিতীয় ত্রৈমাসিক', cs={'2. čtvrtina $date1', case={'gen',''}}, de='zweites Viertel $date1', en='second quarter of $date1', ['en-gb']='second quarter of $date1', ['en-ca']='second quarter of $date1', eo='dua kvarono de $date1', es='segundo cuarto del $date1', fa='ربع دوم $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 toinen neljännes' end, case={'gen',''}}, fr= function(date1, state) return 'deuxième quart '.. frOf[state.precision] ..' $date1' end, he='רבעון שני של $date1', hsb={'druha štwórćina $date1', case={'gen',''}}, hr='druga četvrtina $date1', it='secondo quarto del $date1', ja='$date1第2四半期', ml='$date1 രണ്ടാം ത്രൈമാസം', nb='andre kvartal av $date1', nl='tweede kwart $date1', nn='andre kvartalet av $date1', no='andre kvartal av $date1', pl={'druga ćwiartka $date1', case={'gen',''}}, qu='$date1 iskay ñiqin tawa phatmanpi', ru={'вторая четверть $date1', case={'gen',''}}, sk={'2. štvrtina $date1', case={'gen',''}}, sl={function(date1, state) return 'druga četrtina ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='andra kvartalet av $date1', uk={'2-га чверть $date1', case={'gen',''}}, ur='دوسری سہ ماہی $date1', vi='quý 2 năm $date1', ['zh-hans']='$date1第二季度', ['zh-hant']='$date1第二季度', default='<span style="color:red">second quarter of $date1</span>', }, ['3quarter']= { nDates = 1, ['be-tarask']={'трэцяя чвэрць $date1', case={'gen',''}}, bn='$date1-এর তৃতীয় ত্রৈমাসিক', cs={'3. čtvrtina $date1', case={'gen',''}}, de='drittes Viertel $date1', en='third quarter of $date1', ['en-gb']='third quarter of $date1', ['en-ca']='third quarter of $date1', eo='tria kvarono de $date1', es='tercer cuarto del $date1', fa='ربع سوم $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 kolmas neljännes' end, case={'gen',''}}, fr= function(date1, state) return 'troisième quart '.. frOf[state.precision] ..' $date1' end, he='רבעון שלישי של $date1', hsb={'třeća štwórćina $date1', case={'gen',''}}, hr='treća četvrtina $date1', it='terzo quarto del $date1', ja='$date1第3四半期', ml='$date1 മൂന്നാം ത്രൈമാസം', nb='tredje kvartal av $date1', nl='derde kwart $date1', nn='tredje kvartalet av $date1', no='tredje kvartal av $date1', pl={'trzecia ćwiartka $date1', case={'gen',''}}, qu='$date1 kimsa ñiqin tawa phatmanpi', sk={'3. štvrtina $date1', case={'gen',''}}, sl={function(date1, state) return 'tretja četrtina ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, ru={'третья четверть $date1', case={'gen',''}}, sv='tredje kvartalet av $date1', uk={'3-тя чверть $date1', case={'gen',''}}, ur='تیسری سہ ماہی $date1', vi='quý 3 năm $date1', ['zh-hans']='$date1第三季度', ['zh-hant']='$date1第三季度', default='<span style="color:red">third quarter of $date1</span>', }, ['4quarter']= { nDates = 1, ['be-tarask']={'чацьвёртая чвэрць $date1', case={'gen',''}}, bn='$date1-এর চতুর্থ ত্রৈমাসিক', cs={'4. čtvrtina $date1', case={'gen',''}}, de='letztes Viertel $date1', en='fourth quarter of $date1', ['en-gb']='fourth quarter of $date1', ['en-ca']='fourth quarter of $date1', eo='kvara kvarono de $date1', es='último cuarto del $date1', fa='ربع چهارم $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 viimeinen neljännes' end, case={'gen',''}}, fr= function(date1, state) return 'quatrième quart '.. frOf[state.precision] ..' $date1' end, he='רבעון רביעי של $date1', hsb={'poslednja štwórćina $date1', case={'gen',''}}, hr='četvrta četvrtina $date1', it='quarto quarto del $date1', ja='$date1第4四半期', ml='$date1 നാലാം ത്രൈമാസം', nb='fjerde kvartal av $date1', nl='vierde kwart $date1', nn='tredje kvartalet av $date1', no='fjerde kvartal av $date1', pl={'czwarta ćwiartka $date1', case={'gen',''}}, qu='$date1 qhipaq ñiqin tawa phatmanpi', ru={'последняя четверть $date1', case={'gen',''}}, sk={'4. štvrtina $date1', case={'gen',''}}, sl={function(date1, state) return 'zadnja četrtina ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='fjärde kvartalet av $date1', uk={'4-та чверть $date1', case={'gen',''}}, ur='چوتھی سہ ماہی $date1', vi='quý 4 năm $date1', ['zh-hans']='$date1第四季度', ['zh-hant']='$date1第四季度', default='<span style="color:red">forth quarter of $date1</span>', }, -- ========================================== -- === Seasons ============================== -- ========================================== ['spring'] = { nDates = 1, ar='ربيع $date1', ['be-tarask']='вясна $date1', bn='বসন্তকাল $date1', ca='primavera $date1', cs={'jaro $date1', case={'gen', ''}}, da='Forår $date1', de='Frühling $date1', en='Spring $date1', ['en-gb']='Spring $date1', ['en-ca']='Spring $date1', eo='printempo $date1', es='primavera de $date1', et='$date1 kevad', fa='بهار $date1', fi='Kevät $date1', fr='printemps $date1', gl='primavera $date1', he='אביב $date1', hr='proljeće $date1', hsb='nalěćo $date1', hu='$date1 tavasza', it='primavera $date1', ja='{{#if:$date1|$date1年}}春', mk='пролет $date1', ml='$date1 വസന്തം', nb='våren $date1', nds='Vörjohr $date1', nn='våren $date1', nl='voorjaar $date1', no='våren $date1', pl='wiosna $date1', pt='primavera de $date1', ro='primăvara lui $date1', ru='весна $date1', sk={'jar $date1', case={'gen', ''}}, sl='pomlad $date1', sv='våren $date1', uk='весна $date1', ur='بہار $date1', vi='mùa đông năm $date1', ['zh-hans']='{{#if:$date1|$date1年}}春季', ['zh-hant']='{{#if:$date1|$date1年}}春季', default='<span style="color:red">Spring $date1</span>', }, ['summer'] = { nDates = 1, ar='صيف $date1', ['be-tarask']='лета $date1', bn='গ্রীষ্মকাল $date1', ca='estiu $date1', cs={'léto $date1', case={'gen', ''}}, da='Sommer $date1', de='Sommer $date1', en='Summer $date1', ['en-gb']='Summer $date1', ['en-ca']='Summer $date1', eo='somero $date1', es='verano de $date1', et='$date1 suvi', fa='تابستان $date1', fi='Kesä $date1', fr='été $date1', gl='verán $date1', he='קיץ $date1', hr='ljeto $date1', hsb='lěćo $date1', hu='$date1 nyara', it='estate $date1', ja='{{#if:$date1|$date1年}}夏', mk='лето $date1', ml='$date1 വേനൽ', nb='sommeren $date1', nds='Sommer $date1', nn='sumaren $date1', nl='zomer $date1', no='sommeren $date1', pl='lato $date1', pt='verão de $date1', ro='vara lui $date1', ru='лето $date1', sk={'leto $date1', case={'gen', ''}}, sl='poletje $date1', sv='sommaren $date1', uk='літо $date1', ur='گرما $date1', vi='mùa hè năm $date1', ['zh-hans']='{{#if:$date1|$date1年}}夏季', ['zh-hant']='{{#if:$date1|$date1年}}夏季', default='<span style="color:red">Summer $date1</span>', }, ['autumn'] = { nDates = 1, ar='خريف $date1', ['be-tarask']='восень $date1', bn='শরত্কাল $date1', ca='tardor $date1', cs={'podzim $date1', case={'gen', ''}}, da='Efterår $date1', de='Herbst $date1', en='Autumn $date1', ['en-gb']='Autumn $date1', ['en-ca']='Autumn $date1', eo='aŭtuno $date1', es='otoño de $date1', et='$date1 sügis', fa='پاییز $date1', fi='Syksy $date1', fr='automne $date1', gl='outono $date1', he='סתיו $date1', hr='jesen $date1', hsb='nazyma $date1', hu='$date1 ősze', it='autunno $date1', ja='{{#if:$date1|$date1年}}秋', mk='есен $date1', ml='$date1 ശരത്കാലം', nb='høsten $date1', nds='Harvst $date1', nn='hausten $date1', nl='herfst $date1', no='høsten $date1', pl='jesień $date1', pt='outono de $date1', ro='toamna lui $date1', ru='осень $date1', sk={'jeseň $date1', case={'gen', ''}}, sl='jesen $date1', sv='hösten $date1', uk='осінь $date1', ur='خزاں $date1', vi='mùa thu năm $date1', ['zh-hans']='{{#if:$date1|$date1年}}秋季', ['zh-hant']='{{#if:$date1|$date1年}}秋季', default='<span style="color:red">Autumn $date1</span>', }, ['winter'] = { nDates = 1, ar='شتاء $date1', ['be-tarask']='зіма $date1', bn='শীতকাল $date1', ca='hivern $date1', cs={'zima $date1', case={'gen', ''}}, da='Vinter $date1', de='Winter $date1', en='Winter $date1', ['en-gb']='Winter $date1', ['en-ca']='Winter $date1', eo='vintro $date1', es='invierno de $date1', et='$date1 talv', fa='زمستان $date1', fi='Talvi $date1', fr='hiver $date1', gl='inverno $date1', he='חורף $date1', hr='zima $date1', hsb='zyma $date1', hu='$date1 tele', it='inverno $date1', ja='{{#if:$date1|$date1年}}冬', mk='зима $date1', ml='$date1 ശൈത്യകാലം', nds='Winter $date1', nl='winter $date1', nb='vinteren $date1', nn='vinteren $date1', no='vinteren $date1', pl='zima $date1', pt='inverno de $date1', ro='iarna lui $date1', ru='зима $date1', sk={'zima $date1', case={'gen', ''}}, sl='zima $date1', sv='vintern $date1', uk='зима $date1', ur='سرما $date1', vi='mùa đông năm $date1', ['zh-hans']='{{#if:$date1|$date1年}}冬季', ['zh-hant']='{{#if:$date1|$date1年}}冬季', default='<span style="color:red">Winter $date1</span>', }, -- ========================================== -- === Decade/Century/Millennium ============ -- ========================================== ['decade'] = { nDates = 1, ar='أعوام $date1', ['be-tarask']='$date1-я гады', bn='$date1-এর দশক', ca='dècada del $date1', cs=function(date1, state) return ((date1 % 100 == 0) and 'první dekáda' or ((date1 % 100) .. '. léta')) .. ' ' .. (math.floor(date1 / 100) + 1) .. '. století' end, da="$date1'erne", de='$date1er-Jahre', el='δεκαετία του $date1', en='$date1s', ['en-gb']='$date1s', ['en-ca']='$date1s', eo='$date1-aj jaroj', es='años $date1', et='$date1ndad', fa='دههٔ $date1', fi='$date1-luvulla', fr='années $date1', gl='anos $date1', he='שנות ה-$date1', hr='$date1-ih', hsb='$date1te lěta', hu='$date1-s évek', it="{{#ifexpr: $date1 mod 100 = 0 | primo decennio del {{Roman|{{#expr: ($date1 / 100) + 1}}}} sec. | anni '{{#expr: $date1 mod 100 }} del {{Roman|{{#expr: floor($date1 / 100) + 1}}}} sec.}}", ja='$date1年代', mk='$date1-ти', ml='ദശകം $date1-ൽ', nb='$date1-årene', nn='$date1-åra', nds='Johren $date1', nl='jaren $date1', no='$date1-årene', pl=function(date1, state) local str = 'lata '..(date1 % 100)..'. ' if (date1 % 100 == 0) then str = 'pierwsza dekada ' elseif (date1 % 100 == 10) then str = 'druga dekada ' end return str .. Roman(math.floor(date1 / 100) + 1) .. ' wieku' end, pt='década de $date1', qu='$date1 watakuna', ro='anii $date1', ru='$date1-е годы', sk=function(date1, state) return ((date1 % 100 == 0) and 'prvé desaťročie' or ((date1 % 100) .. '. roky')) .. ' ' .. (math.floor(date1 / 100) + 1) .. '. storočia' end, sl='$date1-ta leta', sv='$date1-talet{{#switch:$date1|1300|1400|1500|1600|1700|1800|1900|2000|2100=&nbsp;(decennium)}}', uk='$date1-ті роки', ur='دہائی $date1', vi='thập niên $date1', ['zh-hans']='$date1年代', ['zh-hant']='$date1年代', default='<span style="color:red">$date1s</span>', }, ['century'] = { nDates = 1, ar='القرن $date1', ['be-tarask']=function(date1, state) return Ordinal(date1, 'be-tarask') .. ' стагодзьдзе' end, bn=function(date1, state) return Ordinal(date1, 'bn') .. ' শতাব্দী' end, ca='segle {{small-caps|{{lc:{{Roman year|$date1}}}}}}', cs='$date1. století', da='$date1. århundrede', de='$date1. Jahrhundert', el='$date1ος αιώνας', en=function(date1, state) return Ordinal(date1, 'en') .. ' century' end, ['en-gb']=function(date1, state) return Ordinal(date1, 'en-gb') .. ' century' end, ['en-ca']=function(date1, state) return Ordinal(date1, 'en-ca') .. ' century' end, eo='$date1-a jarcento', es=function(date1, state) return 'siglo ' .. Roman(date1) end, et='$date1. sajand', fa='سدهٔ $date1', fi='{{#expr: ($date1 - 1) * 100}}-luvulla', fr=function(date1, state) return Ordinal(date1, 'fr', 'roman', 'm') .. ' siècle' end, gl=function(date1, state) return 'século ' .. Roman(date1) end, he='המאה ה-$date1', hr='$date1. stoljeće', hsb='$date1. lětstotk', hu='$date1. évszázad', it=function(date1, state) return Roman(date1) .. ' sec.' end, ja='$date1世紀', la='saeculum $date1', mk=function(date1, state) return Roman(date1) .. ' век' end, ml='$date1-ആം നൂറ്റാണ്ട്', nb=function(date1, state) return Ordinal(date1, 'nb') .. ' århundre' end, nn=function(date1, state) return 'det ' .. Ordinal(date1, 'no') .. ' århundre' end, nds='$date1. Johrhunnert', nl='$date1e eeuw', no=function(date1, state) return Ordinal(date1, 'no') .. ' århundre' end, pl=function(date1, state) return Roman(date1) .. ' ' .. decCentury('pl',state.case) end, pt=function(date1, state) return 'século ' .. Roman(date1) end, qu='$date1 ñiqin pachakwata', ro='secolul al $date1-lea', ru=function(date1, state) return Roman(date1) .. ' век' end, sk='$date1. storočie', sl=function(date1, state) return Ordinal(date1, 'sl') .. '. stoletje' end, sv='{{#expr:$date1 - 1}}00-talet', tt=function(date1, state) return Roman(date1) .. ' гасыр' end, uk=function(date1, state) return Roman(date1) .. ' ст.' end, ur='صدی $date1', vi='thế kỷ $date1', ['zh-hans']='$date1世纪', ['zh-hant']='$date1世紀', default='<span style="color:red">$date1 century</span>', }, ['millennium'] = { nDates = 1, ['be-tarask']=function(date1, state) return Ordinal(date1, 'be-tarask') .. ' тысячагодзьдзе' end, bn=function(date1, state) return Ordinal(date1, 'bn') .. ' সহস্রাব্দ' end, cs='$date1. tisíciletí', de='$date1. Jahrtausend', en=function(date1, state) return Ordinal(date1, 'en') .. ' millennium' end, ['en-gb']=function(date1, state) return Ordinal(date1, 'en-gb') .. ' millennium' end, ['en-ca']=function(date1, state) return Ordinal(date1, 'en-ca') .. ' millennium' end, eo='$date1-a jarmilo', es=function(date1, state) return Roman(date1) .. ' milenio' end, fa=function(date1, state) return 'هزارهٔ ' .. Ordinal(date1, 'fa') end, fr=function(date1, state) return Ordinal(date1, 'fr', 'roman', 'm') .. ' millénaire' end, hr='$date1. tisućljeće', hsb='$date1. lěttysac', it=function(date1, state) return Roman(date1) .. ' millennio' end, ja='$date1千年紀', la='millennium $date1', ml='$date1-മത്തെ സഹസ്രാബ്ദം', nb=function(date1, state) return Ordinal(date1, 'nb') .. ' årtusen' end, nn=function(date1, state) return Ordinal(date1, 'no') .. ' tusenår' end, nl='$date1e millennium', no=function(date1, state) return Ordinal(date1, 'no') .. ' årtusen' end, pl=function(date1, state) return Roman(date1) .. ' ' .. decMillennium('pl',state.case) end, pt=function(date1, state) return Roman(date1) .. ' milénio' end, qu='$date1 ñiqin waranqawata', ru=function(date1, state) return Roman(date1) .. ' тысячелетие' end, sk='$date1. tisícročie', sl=function(date1, state) return Ordinal(date1, 'sl') .. '. tisočletje' end, sv=function(date1, state) return Ordinal(date1, 'sv') .. ' årtusendet' end, uk=function(date1, state) return Roman(date1) .. ' тис.' end, ur=function(date1, state) return 'ہزارہ ' .. Ordinal(date1, 'ur') end, vi='thiên niên kỷ $date1', ['zh-hans']=function(date1, state) return Ordinal(date1, 'zh-hans') .. '千年' end, ['zh-hant']=function(date1, state) return Ordinal(date1, 'zh-hant') .. '千年' end, --default='{{Please translate|$date1 millennium|Module:I18n/complex date}}', default='<span style="color:red">$date1 millennium</span>', }, ['million'] = { de = '$date1 Millionen Jahre', en = '$date1 million years', ['en-gb'] = '$date1 million years', ['en-ca'] = '$date1 million years', eo = '$date1 milionoj da jaroj', es = '$date1 millones de años', nl = '$date1e miljoen jaar', fr = '$date1 millions d’années', it = '$date1 milioni di anni', ml = function(date1, state) if date1 >= 10 then return (date1 / 10) .. ' കോടി വർഷങ്ങൾ' -- 100 lakh = 1 crore else return (date1 * 10) .. ' ലക്ഷം വർഷങ്ങൾ' end end, nb = '$date1 millioner år', nl = '$date1 miljoen jaar', nn = '$date1 millionar år', pl = '$date1 milionów lat', pt = '$date1 milhões de anos', ru = '$date1 миллиона лет', uk = '$date1 мільйони років', ur = '$date1 ملین سال', ['zh-hans'] = '$date1百万年', ['zh-hant'] = '$date1百万年', default='<span style="color:red">$date1 million years</span>', nDates = 1, }, ['billion'] = { de = '$date1 Milliarden Jahre', en = '$date1 billion years', ['en-gb'] = '$date1 billion years', ['en-ca'] = '$date1 billion years', es = '$date1 mil millones de años', nl = '$date1e miljard jaar', fr = '$date1 milliard d’années', it = '$date1 miliardi di anni', ml = function(date1, state) return (date1 * 10) .. ' കോടി വർഷങ്ങൾ' end, nb = '$date1 milliarder år', nl = '$date1 miljard jaar', nn = '$date1 milliardar år', pl = '$date1 miliardów lat', pt = '$date1 mil milhões de anos', ru = '$date1 миллиардов лет', uk = '$date1 мільярдів років', ur = '$date1 بلین سال', ['zh-hans'] = '$date1十亿年', ['zh-hant'] = '$date1十億年', default='<span style="color:red$date1 billion years</span>', nDates = 1, }, -- ========================================== -- === Complex phrases ====================== -- ========================================== -- Phrases which for some languages can be build out of simpler phrases -- Their "default" state, used when no translation in the requested language was found, returns a "state" like -- table which is used to call the function again and built the phrase out of simple phrases -- $date1 - original date - 1 -- $date2 - original date -- if era is BC than $date1 and $date2 are swapped ['turn of the century'] = { nDates = 2, ['be-tarask']=function(date1, date2, state) return 'канец ' .. Roman(date1) ..' — пачатак ' .. Roman(date2) ..' стагодзьдзя' end, bn=function(date1, date2, state) return '$date1/' .. Ordinal(date2, 'bn') .. ' শতাব্দীর পালাবদলে' end, cs='přelom $date1. a $date2. století', de=function(date1, date2, state) return 'Jahrhundertwende $date1/' .. Ordinal(date2, 'de') end, en=function(date1, date2, state) return 'turn of the $date1/' .. Ordinal(date2, 'en') .. ' century' end, ['en-gb']=function(date1, date2, state) return 'turn of the $date1/' .. Ordinal(date2, 'en-gb') .. ' century' end, ['en-ca']=function(date1, date2, state) return 'turn of the $date1/' .. Ordinal(date2, 'en-ca') .. ' century' end, eo='ŝanĝo de la $date1-a al la $date2-a jarcentoj', es=function(date1, date2, state) return 'hacia finales del siglo ' .. Roman(date1) .. ' y principios del siglo ' .. Roman(date2) .. '' end, fi='{{#expr: $date1 * 100-100}}-luvun loppu ja {{#expr: $date2 * 100-100}}-luvun alku', fr=function(date1, date2, state) return 'fin du ' .. Ordinal(date1, 'fr', 'roman', 'm') .. ' siècle / début du ' .. Ordinal(date2, 'fr', 'roman', 'm') end, hr='kraj $date1. i početak $date2. stoljeća', hsb=function(date1, date2, state) return 'přechod ' .. Roman(date1) ..' do ' .. Roman(date2) ..' lětstotka' end, it=function(date1, date2, state) return 'fine del ' .. Roman(date1) .. ' sec. e inizio del ' .. Roman(date2) .. ' sec.' end, ja='$date1世紀から$date2世紀への変わり目', ml='$date1 നൂറ്റാണ്ട് അവസാനവും $date2 നൂറ്റാണ്ട് ആദ്യവും', nb=function(date1, date2, state) return 'slutten av det ' .. Ordinal(date1, 'nb') .. ' århundre/begynnelsen av det ' .. Ordinal(date2, 'nb') .. ' århundre' end, nl=function(date1, date2, state) return 'eind $date1e/begin $date2e eeuw' end, nn=function(date1, date2, state) return 'slutten av det ' .. Ordinal(date1, 'nn') .. ' hundreåret/byrjinga av det ' .. Ordinal(date2, 'nn') .. ' hundreåret' end, pl=function(date1, date2, state) return 'przełom ' .. Roman(date1) ..' i ' .. Roman(date2) ..' wieku' end, qu="$date1 ñiqin waranqawatamanta $date2 ñiqin waranqawataman t'ikrasqanpi", ru=function(date1, date2, state) return 'конец ' .. Roman(date1) ..' — начало ' .. Roman(date2) ..' века' end, sk='prelom $date1. a $date2. storočia', sl=function(date1, date2, state) return 'pozno ' .. Ordinal(date1, 'sl') .. '. in zgodnje ' .. Ordinal(date2, 'sl') .. '. stoletje' end, sv=function(date1, date2, state) return '$date1/' .. Ordinal(date2, 'sv') .. ' årtusendets sekelskifte' end, uk=function(date1, date2, state) return 'кін. ' .. Roman(date1) ..' ст. — поч. ' .. Roman(date2) ..' ст.' end, vi='bước sang thế kỷ $date2', ['zh-hans']=function(date1, date2, state) return '$date1世纪末' .. Ordinal(date2, 'zh-hans') .. '世纪初' end, ['zh-hant']=function(date1, date2, state) return '$date1世紀末' .. Ordinal(date2, 'zh-hant') .. '世紀初' end, default=function(date1, date2, state) return {conj='and', adj1 = 'late', unit1='century', era1=state.era1, adj2 = 'early', unit2='century', era2=state.era2} end, }, -- $date1 - original date - 10 -- $date2 - original date -- if era is BC than $date1 and $date2 are swapped ['turn of the decade']={ nDates = 2, ['be-tarask']='канец $date1-х — пачатак $date2-х гг.', bn='$date1-এর দশকের শেষে এবং $date2-এর দশকের শুরুতে', cs=function(date1, date2, state) local decade1 = math.floor((date1 % 100) / 10) local decade2 = math.floor((date2 % 100) / 10) local century1 = math.floor( date1 / 100) + 1 local century2 = math.floor( date2 / 100) + 1 if decade1 == 0 and date1 < date2 then return "přelom 1. a 2. dekády " .. century1 .. ". století" elseif decade2 == 0 and date2 < date1 then return "přelom 9. a 10. dekády " .. century1 .. ". století" elseif (decade1 == 9 and date1 < date2) or (decade2 == 9 and date2 < date1) then return "přelom " .. century1 .. ". a " .. century2 .. ". století" else return "přelom " .. decade1 .. "0. a " .. decade2 .. "0. let " .. century1 .. ". století" end end, de='späte $date1er und frühe $date2er', en='late $date1s and early $date2s', ['en-gb']='late $date1s and early $date2s', ['en-ca']='late $date1s and early $date2s', eo='ŝanĝo de la $date1-a al la $date2-a jardekoj', es='finales de los años $date1 y principios de los años $date2', fa='اواخر دههٔ $date1 و اوایل دههٔ $date2', fi='$date1-luvun loppu ja $date2-luvun alku', fr='fin des années $date1 / début des années $date2', hr='kraj $date1-ih i početak $date2-ih', hsb='pózdnje $date1te a zažne $date2te lěta', it='fine degli anni $date1 / inizio degli anni $date2', ja='$date1年代末から$date2年代初頭', ml='$date1കൾ അവസാനവും $date2കൾ ആദ്യവും', nb = 'sent på $date1-tallet og tidlig på $date2-tallet', nl = 'eind $date1e/begin $date2e decennium', nn = 'seint på $date1-talet og tidleg på $date2-talet', pl=function(date1, date2, state) local decade1 = math.floor(date1 % 100) local decade2 = math.floor(date2 % 100) local century1 = Roman(math.floor( date1 / 100) + 1) local century2 = Roman(math.floor( date2 / 100) + 1) if decade1 == 0 and date1 < date2 then return "przełom pierwszej i drugiej dekady " .. century1 .. " wieku" elseif decade1 == 10 and date1 < date2 then return "przełom drugiej dekady i lat 20. " .. century1 .. " wieku" elseif decade1 == 90 and decade2 == 0 then return "przełom ostatniej dekady " .. century1 .. " i pierwszej dekady " .. century2 .. " wieku" else return "przełom lat " .. decade1 .. ". i " .. decade2 .. ". " .. century1 .. " wieku" end end, qu='qhipaq $date1 watakuna ñawpaq $date2 watakunapas', ru='конец $date1-х — начало $date2-х гг.', sv='sena $date1-talet och tidiga $date2-talet', sk=function(date1, date2, state) local decade1 = math.floor((date1 % 100) / 10) local decade2 = math.floor((date2 % 100) / 10) local century1 = math.floor( date1 / 100) + 1 local century2 = math.floor( date2 / 100) + 1 if decade1 == 0 and date1 < date2 then return "prelom 1. a 2. desaťročia " .. century1 .. ". storočia" elseif decade2 == 0 and date2 < date1 then return "prelom 9. a 10. desaťročia " .. century1 .. ". storočia" elseif (decade1 == 9 and date1 < date2) or (decade2 == 9 and date2 < date1) then return "prelom " .. century1 .. ". a " .. century2 .. ". storočia" else return "prelom " .. decade1 .. "0. a " .. decade2 .. "0. rokov " .. century1 .. ". storočia" end end, sl='pozna $date1-ta in zgodnja $date2-ta leta', uk='кінець $date1-х — початок $date2-х рр.', ur='اخیر دہائی $date1 و ابتدا دہائی $date2', vi='bước sang thập niên $date2', ['zh-hans']='$date1年代末$date2年代初', ['zh-hant']='$date1年代末$date2年代初', default=function(date1, date2, state) return {conj='and', adj1 = 'late', unit1='decade', era1=state.era1, adj2 = 'early', unit2='decade', era2=state.era2} end, }, -- $date1 - original date - 1 -- $date2 - original date -- if era is BC than $date1 and $date2 are swapped ['turn of the year']={ nDates = 2, ['be-tarask']='канец $date1 — пачатак $date2 г.', bn='$date1-এর শেষে এবং $date2-এর শুরুতে', cs='přelom roku $date1/$date2', de='Jahreswechsel $date1/$date2', en='late $date1 and early $date2', ['en-gb']='late $date1 and early $date2', ['en-ca']='late $date1 and early $date2', eo='jarŝanĝo $date1 / $date2', es='finales de $date1 y principios de $date2', fa='اواخر $date1 و اوایل $date2', fi='vuoden $date1 loppu ja vuoden $date2 alku', fr='fin $date1 / début $date2', hsb='kónc $date1/spočatk $date2', ja='$date1年末から$date2年初め', ml='$date1 അവസാനവും $date2 ആദ്യവും', nb='årsskiftet $date1/$date2', nl='eind $date1 en begin $date2', nn='årsskiftet $date1/$date2', pl='koniec $date1 i początek $date2 roku', qu="$date1 qhipaq p'unchawkuna $date2 ñawpaq p'unchawkunapas", ru='конец $date1 — начало $date2 г.', sk='prelom rokov $date1/$date2', sl='konec leta $date1 in začetek $date2', sv='sena $date1 och tidiga $date2', uk='кінець $date1 — початок $date2 р.', ur='اخیر $date1 و ابتدا $date2', vi='bước sang năm $date2', ['zh-hans']='$date1年末$date2年初', ['zh-hant']='$date1年末$date2年初', default=function(date1, date2, state) return {conj='and', adj1 = 'late', unit1='year', era1=state.era1, adj2 = 'early', unit2='year', era2=state.era2} end, }, ['circa2'] = { nDates = 2, ar='حوالي $date1 – $date2', ['be-tarask']={'прыкладна з $date1 да прыкладна $date2', case={'gen','gen'}}, bn='প্রায় $date1 এবং প্রায় $date2-এর মাঝামাঝি', ca='cap a $date1 o $date2', cs='cca $date1 až $date2', da='ca. mellem $date1 og $date2', de='etwa $date1 bis $date2', el='περίπου μεταξύ $date1 και $date2', en='between circa $date1 and circa $date2', ['en-gb']='between circa $date1 and circa $date2', ['en-ca']='between circa $date1 and circa $date2', eo='proksimume de $date1 ĝis $date2', es='hacia $date1 o $date2', et='ca. $date1 ja $date2 vahel', fa='حدوداً بین $date1 و $date2', fi={function(date1, date2, state) return 'suunnilleen ' .. decYear(state,'fi','xxx') .. '$date1 ja $date2 välisenä aikana' end, case={'gen','gen'}}, fr='vers $date1-$date2', gl='entre $date1 e $date2', he='בסביבות ה- $date1 עד $date2', hr='oko između $date1 i $date2', hsb={'wot něhdźe $date1 do něhdźe $date2', case={'gen','gen'}}, hu='$date1 és $date2 között', it='tra il $date1 e il $date2 circa', ja='$date1頃と$date2頃の間', mk='~ помеѓу $date1 и $date2', ml='ഉദ്ദേശം $date1-നും ഉദ്ദേശം $date2-നും ഇടയ്ക്ക്', nb='ca. mellom $date1 og $date2', nn='ca. mellom $date1 og $date2', nds='üm un bi $date1 bet $date2', nl='ca. $date1 tot $date2', no='ca. mellom $date1 og $date2', pl={'od około $date1 do około $date2', case={'gen','gen'}}, pt='cerca de $date1 a $date2', qu="$date1 p'unchawmantachá $date2 p'unchawkamachá", ro='circa $date1 – $date2', ru={'с около $date1 до, примерно, $date2', case={'gen','gen'}}, sk='cca $date1 až $date2', sl={function(date1, date2, state) return 'ok. ' .. decYear(state,'sl','gen') .. ' $date1 do $date2' end, case={'gen','gen'}}, sv='c:a $date1 till $date2', uk={'з близько $date1 до, приблизно, $date2', case={'gen','gen'}}, ur='قریباً بین $date1 و $date2', vi='giữa vào khoảng $date1 và vào khoảng $date2', ['zh-hans']='约$date1~$date2间', ['zh-hant']='約$date1~$date2間', default=function(date1, date2, state) return {conj='between', adj1 = 'circa', unit1=state.unit1, era1=state.era1, adj2 = 'circa', unit2=state.unit2, era2=state.era2} end, }, -- ========================================== -- === Eras and calendars =================== -- ========================================== ['julian'] = { nDates = 2, ar='$date1 ($date2 حسب [[w:ar:التقويم اليولياني|تقويم يولياني]])', ['be-tarask']='$date1 ($date2 паводле [[w:be-tarask:Юліянскі каляндар|ст. ст.]]', bn='$date1 (জুলিয়ান ক্যালেন্ডারে $date2)', ca='$date1 ($date2 segons el [[w:ca:Calendari julià|calendari julià]])', cs='$date1 ($date2 podle [[w:cs:Juliánský kalendář|juliánského kalendáře]])', da='$date1 ($date2 [[w:da:Julianske kalender|Julianske kalender]])', de='$date1 ($date2 [[w:de:Julianischer Kalender|Julianischer Kalender]])', el='$date1 ($date2 με το [[w:el:Ιουλιανό ημερολόγιο|Ιουλιανό ημερολόγιο]])', en='$date1 ($date2 in [[w:Old Style and New Style dates|Julian calendar]])', ['en-gb']='$date1 ($date2 in [[w:Old Style and New Style dates|Julian calendar]])', ['en-ca']='$date1 ($date2 in [[w:Old Style and New Style dates|Julian calendar]])', fa='$date1 ($date2 در [[w:fa:Old Style and New Style dates|تقویم جولیان]])', eo='$date1 ($date2 laŭ la [[w:eo:Julia kalendaro|Julia Kalendaro]])', es='$date1 ($date2 según el [[w:es:Calendario juliano|calendario juliano]])', et='$date1 ($date2 [[w:et:Juliuse kalender|Juliuse kalendris]])', fi='$date1 ($date2 [[w:fi:juliaaninen kalenteri|juliaanisen kalenterin]] mukaan)', fr='$date1 ($date2 dans le [[w:fr:calendrier julien|calendrier julien]])', gl='$date1 ($date2 segundo o [[w:gl:Calendario xuliano|calendario xuliano]])', hu='$date1 ($date2 a [[w:hu:Julianus-naptár|Julián naptárban]])', hr='$date1 ($date2 prema [[w:hr:Julijanski kalendar|julijanskom kalendaru]])', he='$date1 ($date2 ב[[w:he:לוח היוליאני|הלוח היוליאני]])', hsb='$date1 ($date2 po julianiskej protyce)', it='$date1 ($date2 secondo il [[w:it:Calendario giuliano|calendario giuliano]])', ja='$date1([[w:ja:ユリウス暦|ユリウス暦]] $date2)', mk='$date1 ($date2 по [[w:mk:Јулијански календар|Јулијанскиот календар]])', ml='$date1 ([[w:ml:ജൂലിയൻ കാലഗണനാരീതി|ജൂലിയൻ വർഷത്തിൽ]] $date2)', ml='$date1 ([[w:Old Style and New Style dates|ജൂലിയൻ കാലഗണനാരീതിയിൽ]] $date2)', nds='$date1 ($date2 na’n [[w:nds:Juliaansch Klenner|Juliaanschen Klenner]])', nb='$date1 ($date2 etter [[w:no:Juliansk kalender|den julianske kalenderen]])', nn='$date1 ($date2 etter [[w:no:Juliansk kalender|den julianske kalenderen]])', nl='$date1 ($date2 volgens de [[w:nl:Juliaanse kalender|Juliaanse kalender]])', no='$date1 ($date2 etter [[w:no:Juliansk kalender|den julianske kalender]])', pl='$date1 ($date2 wedle [[w:pl:Daty nowego i starego porządku|kalendarza juliańskiego]])', pt='$date1 ($date2 no [[w:pt:Mudança para o calendário gregoriano|calendário juliano]])', qu='$date1 ($date2 [[w:qu:Hulyanu kalindaryu|Hulyanu kalindaryupi]])', ro='$date1 ($date2 în [[w:ro:calendarul iulian|calendarul iulian]])', ru='$date1 ($date2 [[w:ru:Юлианский календарь|по ст. ст.]])', sk='$date1 ($date2 podľa [[w:sk:Juliánsky kalendár|juliánskeho kalendára]])', sl='$date1 ($date2 po [[w:sl:Julijanski koledar|julijanskem koledarju]])', sv='$date1 ($date2 [[w:sv:Gamla stilen|gamla stilen]])', uk='$date1 ($date2 [[w:uk:Юліанський календар|Юліанський календар]])', ur='$date1 ($date2 حسب [[w:ur:نئے اور پانے انداز کی تاریخ|جولین تقویم]])', vi='$date1 ($date2 trong [[w:vi:Lịch Julius|lịch Juliêng]])', ['zh-hans']='$date1([[w:Old Style and New Style dates|格里历]]$date2)', ['zh-hant']='$date1([[w:Old Style and New Style dates|格里曆]]$date2)', default='<span style="color:red">$date1 ($date2 in [[w:Old Style and New Style dates|Julian calendar]])</span>', }, ['ad'] = { nDates = 1, ['be-tarask']='$date1 [[w:be-tarask:ад Нараджэньня Хрыста|пасьля Н. Х.]]', bn ='$date1 খ্রিস্টাব্দ', ca ='$date1 dC', cs ='$date1 n. l.', de ='$date1 n. Chr.', el ='$date1 μ.Χ.', en ='$date1 [[w:en:Anno Domini|AD]]', ['en-gb'] ='$date1 [[w:en:Anno Domini|AD]]', ['en-ca'] ='$date1 [[w:en:Anno Domini|AD]]', eo ='$date1 pK', es ='$date1 [[w:es:Anno Dómini|d. C.]]', fa ='$date1 م.', fi ='$date1 jaa.', fr ='$date1 ap. J.-C.', gl ='$date1 d.C.', he ='$date1 לספירה', hu ='i. sz. $date1', hr ='$date1 p. Kr.', hsb='$date1 po Chr.', it ='$date1 d.C.', ja ='西暦$date1', mk ='$date1 н.е.', ml ='ക്രി.വ. $date1', nb ='$date1 e.Kr.', nl ='$date1 n.Chr.', nn ='$date1 e.Kr.', no ='$date1 e.Kr.', pl ='$date1 [[w:pl:naszej ery|n.e.]]', ro ='$date1 d.Hr.', ru ='$date1 н. э.', sk ='$date1 po Kr.', sl ='$date1 n. št.', sv ='$date1 [[w:sv:Efter Kristus|e.Kr.]]', uk ='$date1 н. е.', ur ='$date1 ء', vi ='$date1 [[w:vi:Công Nguyên|CN]]', ['zh-hans'] ='[[w:zh:基督纪年|公元]]$date1', ['zh-hant'] ='[[w:zh:基督纪年|公元]]$date1', default='<span style="color:red">$date1 [[w:en:Anno Domini|AD]]</span>', }, ['ah'] = { nDates = 1, ['be-tarask']='$date1 [[w:be-tarask:Ісламскі каляндар|хіджры]]', bn ='$date1 [[w:bn:ইসলামি বর্ষপঞ্জি|হিজরী]]', cs ='$date1 [[w:cs:Anno Hegirae|AH]]', en ='$date1 [[w:en:Hijri year|AH]]', ['en-gb'] ='$date1 [[w:en:Hijri year|AH]]', ['en-ca'] ='$date1 [[w:en:Hijri year|AH]]', eo ='$date1 post la [[w:eo:Heĝiro|Heĝiro]]', fa ='$date1 ه.ق.', fr ='$date1 [[w:fr:Calendrier musulman|A.H.]]', he ="$date1 [[w:he:הלוח המוסלמי|להג'רה]]", hr ='$date1 god. hidžre', ja ='[[w:ja:ヒジュラ暦|ヒジュラ暦]]$date1', ml ='$date1 [[w:ml:ഇസ്‌ലാമിക കലണ്ടർ|ഹിജ്റ]]', nb ='$date1 [[w:no:Den muslimske kalenderen|A.H.]]', nn ='$date1 [[w:nn:Den muslimske kalenderen|A.H.]]', pl ='$date1 [[w:pl:Kalendarz muzułmański|A.H.]]', ru ='$date1 [[w:ru:Исламский календарь|хиджры]]', sk ='$date1 [[w:sk:A. H.|A. H.]]', sv ='$date1 [[w:sv:Muslimska kalendern|A.H.]]', uk ='$date1 [[w:uk:Мусульманський календар|хіджри]]', ur ='$date1 ھ', vi ='$date1 [[w:vi:Lịch Hồi giáo|AH]]', ['zh-hans'] ='[[w:en:Hijri year|Hijri历]]$date1', ['zh-hant'] ='[[w:en:Hijri year|Hijri曆]]$date1', default='<span style="color:red">$date1 [[w:en:Hijri year|AH]]</span>', }, ['bc'] = { nDates = 1, ar ='$date1 ق.م.', ['be-tarask']='$date1 [[w:be-tarask:да Нараджэньня Хрыста|да Н. Х.]]', bn ='খ্রিস্টপূর্ব $date1', ca ='$date1 [[w:ca:Abans de Crist|aC]]', cs ='$date1 [[w:cs:Př. n. l.|př. n. l.]]', de ='$date1 [[w:de:v. Chr.|v. Chr.]]', el ='$date1 π.Χ.', en ='$date1 [[w:en:Ante Christum Natum|BC]]', ['en-gb'] ='$date1 [[w:en:Ante Christum Natum|BC]]', ['en-ca'] ='$date1 [[w:en:Ante Christum Natum|BC]]', eo ='$date1 aK', es ='$date1 [[w:es:Antes de Cristo|a. C.]]', fa ='$date1 پ.م.', fi ='$date1 eaa.', fr ='$date1 av. J.-C.', gl ='$date1 [[w:gl:Antes de Cristo|a.C.]]', he ='$date1 לפני הספירה', hr ='$date1 [[w:hr:Pr. Kr.|pr. Kr.]]', hsb='$date1 do Chr.', hu ='i. e. $date1', it ='$date1 [[w:it:Avanti Cristo|a.C.]]', ja ='[[w:ja:紀元前|紀元前]]$date1', mk ='$date1 п.н.е.', ml ='ക്രി.മു. $date1', nds='$date1 vör uns Tied', nb ='$date1 f.Kr.', nl ='$date1 v.Chr.', nn ='$date1 f.Kr.', no ='$date1 f.Kr.', pl ='$date1 [[w:pl:przed naszą erą|p.n.e.]]', ro ='$date1 î.Hr.', ru ='$date1 до н. э.', sk ='$date1 [[w:sk:Pred Kristom|pred Kr.]]', sl ='$date1 pr. n. št.', sv ='$date1 [[w:sv:Före Kristus|f.Kr.]]', uk ='$date1 до н. е.', ur ='$date1 ق م', vi ='$date1 [[w:vi:Trước Công Nguyên|TCN]]', ['zh-hans'] ='[[w:zh:基督纪年|公元前]]$date1', ['zh-hant'] ='[[w:zh:基督纪年|公元前]]$date1', default='<span style="color:red">$date1 [[w:en:Ante Christum Natum|BC]]</span>', }, ['bp'] = { nDates = 1, ar ='$date1 [[w:ar:قبل الحاضر|قبل الحاضر]]', be ='$date1 [[w:be-tarask:Да нашых дзён|Да нашых дзён]]', ['be-tarask']='$date1 [[w:be-tarask:Да нашых дзён|Да нашых дзён]]', bn ='বর্তমানের আগে $date1', ca ='$date1 [[w:ca:BP (unitat de temps)|BP]]', cs ='$date1 [[w:cs:B. P.|B. P.]]', de ='$date1 [[w:de:Before Present|BP]]', el ='$date1 [[w:en:Before Present|BP]]', en ='$date1 [[w:en:Before Present|BP]]', ['en-gb'] ='$date1 [[w:en:Before Present|BP]]', ['en-ca'] ='$date1 [[w:en:Before Present|BP]]', eo='$date1 [[w:eo:Before Present|BP]]', es ='$date1 [[w:es:Before Present|BP]]', et ='$date1 [[w:et:Before Present|BP]]', fa ='$date1 پیش از اکنون', fi ='$date1 [[w:fi:BP (ajanlasku)|BP]]', fr ='$date1 [[w:fr:Avant le présent|avant le présent]]', hr ='$date1 [[w:hr:BP|prije današnjice]]', it ='$date1 [[w:it:Before Present|BP]]', ja ='$date1 [[w:ja:BP (年代測定)|BP]]', lv ='$date1 [[w:lv:Pirms mūsdienām|Pirms mūsdienām]]', mk ='$date1 [[w:mk:Пред сегашноста|п.с.]]', ml ='[[w:en:Before Present|ഇന്നത്തെ കാലത്തിനു മുമ്പ്]] $date1', nb ='$date1 [[w:no:Before Present|BP]]', nn ='$date1 [[w:nn:Before Present|BP]]', nl ='$date1 [[w:nl:Before Present|BP]]', no ='$date1 [[w:no:Before Present|BP]]', pl ='$date1 [[w:pl:Before Present|BP]]', pt ='$date1 [[w:pt:Antes do Presente|Antes do Presente]]', ru ='$date1 [[w:ru:До настоящего времени|до н. в.]]', sk ='$date1 [[w:sk:BP (čas)|BP]]', sl ='$date1 [[w:sl:pred sedanjostjo|p. s.]]', sr ='$date1 [[w:sr:Прије садашњости|Прије садашњости]]', sv ='$date1 [[w:sv:B.P.|B.P.]]', uk ='$date1 [[w:uk:Років тому|Років тому]]', ur ='$date1 قبل حاضر', vi ='$date1 [[w:en:Before Present|trước ngày nay]]', ['zh-hans'] ='[[w:zh:距今|距今]]$date1', ['zh-hant'] ='[[w:zh:距今|距今]]$date1', default='<span style="color:red">$date1 [[w:en:Before Present|BP]]</span>', }, } -- List of possible string inputs to "adj", "junction", "era", and "precision" fields -- Each term is matched to its synonym and the role: a - adjective, e - era -- p - precision, c - calendar and combos, r - certainty and j - conjunction p.Synonyms = { -- adjectives and prepositions ['quarter1']={'1quarter','a'}, ['1stquarter']={'1quarter','a'}, ['1st quarter']={'1quarter','a'}, ['first quarter']={'1quarter','a'}, ['1quarter']={'1quarter','a'}, ['quarter2']={'2quarter','a'}, ['2ndquarter']={'2quarter','a'}, ['2nd quarter']={'2quarter','a'}, ['second quarter']={'2quarter','a'}, ['2quarter']={'2quarter','a'}, ['quarter3']={'3quarter','a'}, ['3rdquarter']={'3quarter','a'}, ['3rd quarter']={'3quarter','a'}, ['third quarter']={'3quarter','a'}, ['3quarter']={'3quarter','a'}, ['quarter4']={'4quarter','a'}, ['4thquarter']={'4quarter','a'}, ['4th quarter']={'4quarter','a'}, ['fourth quarter']={'4quarter','a'}, ['4quarter']={'4quarter','a'}, ['spring']={'spring','a'}, ['summer']={'summer','a'}, ['fall']={'autumn','a'}, ['autumn']={'autumn','a'}, ['winter']={'winter','a'}, ['beginning']={'early','a'}, ['early']={'early','a'}, ['1half']={'firsthalf','a'}, ['1sthalf']={'firsthalf','a'}, ['1st half']={'firsthalf','a'}, ['firsthalf']={'firsthalf','a'}, ['first half']={'firsthalf','a'}, ['end']={'late','a'}, ['late']={'late','a'}, ['middle']={'mid','a'}, ['mid']={'mid','a'}, ['2half']={'secondhalf','a'}, ['2ndhalf']={'secondhalf','a'}, ['2nd half']={'secondhalf','a'}, ['secondhalf']={'secondhalf','a'}, ['second half']={'secondhalf','a'}, ['<']={'before','a'}, ['b']={'before','a'}, ['before']={'before','a'}, ['>']={'after','a'}, ['a']={'after','a'}, ['after']={'after','a'}, ['~']={'circa','a'}, ['c']={'circa','a'}, ['ca']={'circa','a'}, ['circa']={'circa','a'}, ['by']={'by','a'}, ['from']={'from','a'}, ['until']={'until','a'}, ['uncertain']={'uncertain','r'}, ['possibly']={'possibly','r'}, ['probably']={'probably','r'}, ['presumably']={'presumably','r'}, -- eras ['bc']={'bc','e'}, ['bce']={'bc','e'}, ['before present']={'bp','e'}, ['bp']={'bp','e'}, ['ce']={'ad','e'}, ['ad']={'ad','e'}, ['ah']={'ah','e'}, -- conjunctions ['&']={'and','j'}, ['and']={'and','j'}, ['or']={'or','j'}, ['-']={'from-until','j'}, ['–']={'from-until','j'}, ['from-until']={'from-until','j'}, ['between']={'between','j'}, ['circa2']={'circa2','a'}, -- precision ['day']={'day','p'}, ['month']={'month','p'}, ['year']={'year','p'}, ['s']={'decade','p'}, ['decade']={'decade','p'}, ['century']={'century','p'}, ['millennium']={'millennium','p'}, ['million']={'million','p'}, ['billion']={'billion','p'}, -- calendar ['julian']={'julian','c'}, ['islamic']={'islamic','c'}, ['hijri']={'islamic','c'}, -- "Turn of the ..." options are a combos which will be handled in similar way as calendars ['turn of the century']={'turn of the century','c'}, ['century turn']={'turn of the century','c'}, ['turn of the decade']={'turn of the decade','c'}, ['decade turn']={'turn of the decade','c'}, ['turn of the year']={'turn of the year','c'}, ['year turn']={'turn of the year','c'}, -- other dates which will be handled in similar way as calendars ['?']={'unknown','c'}, ['unknown']={'unknown','c'}, ['unknown year']={'year unknown','c'}, ['year unknown']={'year unknown','c'}, } return p a3jszs2h8krglns3fs0hdwxtma3dco2 803610 803609 2019-07-28T14:17:09Z en>RexxS 0 Add ckb functionality per edit request - copied from [[:ckb:Module:I18n/complex date]] 28 July 2019 803610 Scribunto text/plain --[[ __ __ _ _ ___ _ ___ __ _ _ _ | \/ | ___ __| |_ _| | ___ _|_ _/ |( _ ) _ __ / /__ ___ _ __ ___ _ __ | | _____ __ __| | __ _| |_ ___ | |\/| |/ _ \ / _` | | | | |/ _ (_)| || |/ _ \| '_ \ / / __/ _ \| '_ ` _ \| '_ \| |/ _ \ \/ / / _` |/ _` | __/ _ \ | | | | (_) | (_| | |_| | | __/_ | || | (_) | | | |/ / (_| (_) | | | | | | |_) | | __/> < | (_| | (_| | || __/ |_| |_|\___/ \__,_|\__,_|_|\___(_)___|_|\___/|_| |_/_/ \___\___/|_| |_| |_| .__/|_|\___/_/\_\ \__,_|\__,_|\__\___| |_| This code was copied from Commons please request changes there. Maintainers: *Jarekt ]] local function Ordinal(...) return require('Module:Ordinal-cd')._Ordinal(...) end local function Roman(...) return require('Module:Roman-cd')._Numeral(...) end -- ========================================================================== -- === Local Helper functions ============================================== -- ========================================================================== -- form of French "of" based on "precision", which is wikibase compatible date precision format: -- 6=millennium, 7=century, 8=decade, 9=year, 10=month, 11=day -- 1 2 3 4 5 mill cent deca year month day local frOf = {'du','du','du','du','du', 'du', 'du', 'de', 'de', 'de', 'du'} local function decYear(state,lang,case) --Declension of word "year" in several languages if state.precision == 9 then yearCase = { fi={xxx='vuosien', gen='vuoden', ill='vuoteen', ptv='vuotta', ela='vuodesta', ine='vuonna'}, sl={insP='letoma', ins='letom', gen='leta', loc='letu'}, hsb={insP='lětami', ins='lětom', gen='lěta', loc='lěće'} } return yearCase[lang][case] .. ' ' else return '' end end local function decCentury(lang,case) --Declension of word "century" in several languages centuryCase = { pl={nom='wiek', gen='wieku', ins='wiekiem', loc='wieku', default = 'wiek'}, hsb={nom='lětstotk', gen='lětstotka', ins='lětstotkom', loc='lětstotku', default = 'lětstotk'} } return centuryCase[lang][case] or centuryCase[lang]['default'] end local function decMillennium(lang,case) --Declension of word "century" in several languages millenniumCase = { pl={nom='tysiąclecie', gen='tysiąclecia', ins='tysiącleciem', loc='tysiącleciu', default = 'tysiąclecie'}, hsb={nom='lěttysac', gen='lěttysaca', ins='lěttysacom', loc='lěttysacu', default = 'lěttysac'} } return millenniumCase[lang][case] or millenniumCase[lang]['default'] end -- ========================================================================== -- === Output data structure =============================================== -- ========================================================================== local p = {} --[[ Multiple formats are possible: 1) string with key words "$date1" and "$date2" which will be replaced later by the date strings 2) function which can be used to create output string * Function INPUTS: - date1 and date2 string with date strings. "$date1" and "$date2" can also be used which will be replaced latter - state is a table with full description of the phrase we are trying to construct. It includes state.precision1 (and 2) which is wikibase compatible date precision format: 6=millennium, 7=century, 8=decade, 9=year, 10=month, 11=day 3) table with following fields: * table[1] is either string or a function * table.case is a 2 element table with Grammatical cases used by given language to alter month name or other adjective related to date # 1 and 2 ]] p.Translations = { -- ========================================== -- === Two date phrases ===================== -- ========================================== ['from-until'] = { nDates = 2, ar='من $date1 إلى $date2', ['be-tarask']={'з $date1 да $date2', case={'gen','gen'}}, bn='$date1 থেকে $date2 পর্যন্ত', ca='$date1 fins $date2', ckb='لە $date1 تاوەکوو $date2', cs='$date1 až $date2', da='fra $date1 til $date2', de='$date1 bis $date2', el='από $date1 έως $date2', en='from $date1 until $date2', ['en-gb']='from $date1 until $date2', ['en-ca']='from $date1 until $date2', eo='de $date1 al $date2', es='de $date1 a $date2', et='$date1 kuni $date2', fa='از $date1 تا $date2', fi='$date1–$date2', -- välisenä aikana fr=function(date1, date2, state) -- could actually be better should be "d'octobre à mars 2013' not "de octore 2013 à mars 2013" if state.precision > 10 then -- precision > month return 'du $date1 au $date2' else -- "DE septebmbre" / "D'octobre" return require("Module:Linguistic").of(date1, 'fr') .. " à $date2" end end, gl='de $date1 a $date2', --Template:From until used 'desde $date1 ata $date2' he='מ- $date1 עד $date2', hr='od $date1 do $date2', hsb={'wot $date1 do $date2', case={'gen','gen'}}, hu='$date1 – $date2', it='dal $date1 al $date2', ja='$date1から$date2まで', mk='од $date1 до $date2', ml='$date1 മുതൽ $date2 വരെ', nb='fra $date1 til $date2', nds='$date1 bet $date2', nn='fra $date1 til $date2', nl='$date1 tot $date2', no='fra $date1 til $date2', pl={'od $date1 do $date2', case={'gen','gen'}}, ro='din $date1 până în $date2', pt='de $date1 a $date2', qu="$date1 p'unchawmanta $date2 p\'unchawkama", ru={'с $date1 по $date2', case={'gen','gen'}}, sk={'od $date1 do $date2', case={'gen','gen'}}, sl={'od $date1 do $date2', case={'gen','gen'}}, sv='från $date1 till $date2', uk={'з $date1 до $date2', case={'gen','gen'}}, ur='از $date1 تا $date2', vi='từ $date1 đến $date2', ['zh-hans']='从$date1到$date2', ['zh-hant']='從$date1到$date2', default='<span style="color:red">$date1 - $date2</span>', }, ['between']={ nDates = 2, ar='بين $date1 و $date2', ['be-tarask']={'між $date1 і $date2', case={'ins','ins'}}, bn='$date1 এবং $date2-এর মাঝামাঝি', ca='entre $date1 i $date2', ckb='لەنێوان $date1 و $date2', cs={'mezi $date1 a $date2', case={'ins','ins'}}, da='mellem $date1 og $date2', de='zwischen $date1 und $date2', el='μεταξύ $date1 και $date2', en='between $date1 and $date2', ['en-gb']='between $date1 and $date2', ['en-ca']='between $date1 and $date2', eo='inter $date1 kaj $date2', es='entre $date1 y $date2', et='$date1 ja $date2 vahel', fa='بین $date1 و $date2', fi={function(date1, date2, state) return decYear(state,'fi','xxx') .. '$date1 ja $date2 välisenä aikana' end, case={'gen','gen'}}, fr='entre $date1 et $date2', gl='entre $date1 e $date2', he='בין $date1 ו- $date2', hr='između $date1 i $date2', hsb={'mjez $date1 a $date2', case={'ins','ins'}}, hu='$date1 és $date2 között', hi='$date1 और $date2 के बीच', it='tra il $date1 e il $date2', ja='$date1と$date2の間', mk='помеѓу $date1 и $date2', ml='$date1, $date2 എന്നിവയ്ക്കിടയ്ക്ക്', nb='mellom $date1 og $date2', nds='twischen $date1 un $date2', nn='mellom $date1 og $date2', nl='tussen $date1 en $date2', no='mellom $date1 og $date2', pl={'między $date1 a $date2', case={'ins','ins'}}, ro='între $date1 și $date2', pt='entre $date1 e $date2', qu="$date1 p'unchawpa $date2 p'unchawpapas chawpin", ru={'между $date1 и $date2', case={'ins','ins'}}, sk={'medzi $date1 a $date2', case={'ins','ins'}}, sl={function(date1, date2, state) return 'med ' .. decYear(state,'sl','insP') .. '$date1 in $date2' end, case={'ins','ins'}}, sv='mellan $date1 och $date2', uk={'між $date1 та $date2', case={'ins','ins'}}, ur='بین $date1 و $date2', vec='tra el $date1 e el $date2', vi='giữa $date1 và $date2', ['zh-hans']='在$date1到$date2之间', ['zh-hant']='在$date1到$date2之間', default='<span style="color:red">$date1 - $date2</span>', }, ['or'] = { nDates = 2, ar='$date1 أو $date2', ['be-tarask']='$date1 ці $date2', bn='$date1 অথবা $date2', ca='$date1 o $date2', ckb='$date1 یان $date2', cs='$date1 nebo $date2', da='$date1 eller $date2', de='$date1 oder $date2', el='$date1 ή $date2', en='$date1 or $date2', ['en-gb']='$date1 or $date2', ['en-ca']='$date1 or $date2', eo='$date1 aŭ $date2', es='$date1 o $date2', et='$date1 või $date2', fa='$date1 یا $date2', fi='$date1 tai $date2', fr='$date1 ou $date2', gl='$date1 ou $date2', he='$date1 או $date2', hr='$date1 ili $date2', hsb='$date1 abo $date2', hu='$date1 vagy $date2', it='$date1 o $date2', ja='$date1または$date2', mk='$date1 или $date2', ml='$date1 അഥവാ $date2', nb='$date1 eller $date2', nn='$date1 eller $date2', nds='$date1 oder $date2', nl='$date1 of $date2', no='$date1 eller $date2', pl='$date1 lub $date2', pt='$date1 ou $date2', qu='$date1 icha $date2', ro='$date1 sau $date2', ru='$date1 или $date2', sk='$date1 alebo $date2', sl='$date1 ali $date2', sv='$date1 eller $date2', uk='$date1 або $date2', ur='$date1 یا $date2', th='$date1 หรือ $date2', vi='$date1 hoặc $date2', ['zh-hans']='$date1或$date2', ['zh-hant']='$date1或$date2', default=function(date1, date2, state) return require("Module:linguistic").conj({date1, date2}, state.lang, 'or') end, }, ['and'] = { nDates = 2, ar='$date1 و $date2', ['be-tarask']='$date1 і $date2', bn='$date1 এবং $date2', ca='$date1 i $date2', ckb='$date1 و $date2', cs='$date1 a $date2', da='$date1 og $date2', de='$date1 und $date2', el='$date1 και $date2', en='$date1 and $date2', ['en-gb']='$date1 and $date2', ['en-ca']='$date1 and $date2', eo='$date1 kaj $date2', es='$date1 y $date2', et='$date1 ja $date2', fa='$date1 و $date2', fi='$date1 ja $date2', fr='$date1 et $date2', gl='$date1 e $date2', he='$date1 ו- $date2', hu='$date1 és $date2', hsb='$date1 a $date2', it='$date1 e $date2', ja='$date1および$date2', mk='$date1 и $date2', ml='$date1 ഒപ്പം $date2', nb='$date1 og $date2', nn='$date1 og $date2', nds='$date1 un $date2', nl='$date1 en $date2', no='$date1 og $date2', pl='$date1 i $date2', pt='$date1 e $date2', qu='$date1wan $date2pas', ro='$date1 și $date2', ru='$date1 и $date2', sk='$date1 a $date2', sl='$date1 in $date2', sv='$date1 och $date2', uk='$date1 та $date2', ur='$date1 و $date2', vi='$date1 và $date2', ['zh-hans']='$date1和$date2', ['zh-hant']='$date1和$date2', default=function(date1, date2, state) return require("Module:linguistic").conj({date1, date2}, state.lang, 'and') end, }, -- ========================================== -- === Prepositions ========================= -- ========================================== ['by'] = { nDates = 1, ar='حوالي $date1', ['be-tarask']={'да $date1', case={'gen',''}}, bn='$date1 কর্তৃক', ca='vers $date1', ckb='نزیکی $date1', cs={'kolem $date1', case={'gen',''}}, da='mod $date1', de='spätestens $date1', el='έως $date1', en='by $date1', ['en-gb']='by $date1', ['en-ca']='by $date1', eo='ne post $date1', es='para $date1', et='hiljemalt $date1', fa='توسط $date1', fi={function(date1, state) return decYear(state,'fi','ill') .. '$date1 mennessä' end, case={'ill',''}}, fr='$date1 (au plus tard)', gl='cara a $date1', he='עד $date1', hr='do $date1', hsb={'do $date1', case={'gen',''}}, it='entro il $date1', ja='$date1まで', mk='кон $date1', ml='$date1-ൽ', nb='innen $date1', nn='innan $date1', nds='$date1 oder vördem al', nl='tegen $date1', no='innen $date1', pl={'przed $date1', case={'ins',''}}, pt='por $date1', qu="$date1 p'unchawkama", ro='de $date1', ru={'к $date1', case={'dat',''}}, sk={'okolo $date1', case={'gen',''}}, sl={function(date1, state) return 'najpozneje ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='senast $date1', uk={'до $date1', case={'dat',''}}, ur='توسط $date1', vi='vào $date1', -- need to insert "đã" somewhere later in the sentence --> ['zh-hans']='$date1前后', ['zh-hant']='$date1前后', default='<span style="color:red">by $date1</span>', }, ['before'] = { nDates = 1, ar='قبل $date1', ['be-tarask']={'перад $date1', case={'ins',''}}, bn='$date1-এর আগে', bg='Преди $date1', ca='abans $date1', ckb='پێش $date1', cs={'před $date1', case={'ins',''}}, da='før $date1', de='vor $date1', el='πριν από $date1', en='before $date1', ['en-gb']='before $date1', ['en-ca']='before $date1', eo='antaŭ $date1', es='antes de $date1', et='enne $date1', fa='پیش از $date1', fi={function(date1, state) return 'ennen ' .. decYear(state,'fi','ptv') .. '$date1' end, case={'ptv',''}}, fr='avant $date1', gl='antes do $date1', he='לפני $date1', hr='prije $date1', hsb={'do $date1', case={'gen',''}}, hu='$date1 előtt', it='prima del $date1', ja='$date1より前', mk='пред $date1', ml='$date1-നു മുമ്പ്', nb='før $date1', nn='før $date1', nds='vör $date1', nl='voor $date1', no='før $date1', pl={'przed $date1', case={'ins',''}}, pt='antes de $date1', qu="$date1 p'unchaw ñawpan", ro='înainte de $date1', ru={'ранее $date1', case={'gen',''}}, sk={'pred $date1', case={'ins',''}}, sl={function(date1, state) return 'pred ' .. decYear(state,'sl','ins') .. ' $date1' end, case={'ins',''}}, sv='före $date1', uk={'до $date1', case={'gen',''}}, ur='قبل $date1', vi='trước $date1', ['zh-hans']='在$date1之前', ['zh-hant']='在$date1之前', default='<span style="color:red">before $date1</span>', }, ['from'] = { nDates = 1, ar='من $date1', ['be-tarask']={'з $date1', case={'gen',''}}, bn='$date1 থেকে', ckb='لە $date1', cs={'od $date1', case={'gen',''}}, de='Seit $date1', el='από $date1', en='from $date1', ['en-gb']='from $date1', ['en-ca']='from $date1', eo='ekde $date1', es='desde $date1', et='alates $date1', fa='از $date1', fi={function(date1, state) return decYear(state,'fi','ela') .. '$date1 alkaen' end, case={'ela',''}}, fr= function(date1, state) return 'à partir '.. frOf[state.precision] ..' $date1' end, gl='desde o $date1', he='מ- $date1', hr='od $date1', hsb={'wot $date1', case={'gen',''}}, it='dal $date1', ja='$date1から', nds='vonaf $date1', mk='од $date1', nb='fra $date1', nn='frå $date1', ml='$date1 മുതൽ', nl='vanaf $date1', no='fra $date1', pl={'od $date1', case={'gen',''}}, qu="$date1 p'unchawmanta", ro='din $date1', ru={'С $date1', case={'gen',''}}, sk={'od $date1', case={'gen',''}}, sl={function(date1, state) return 'od ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='från $date1', uk={'з $date1', case={'gen',''}}, ur='از $date1', vi='từ $date1', ['zh-hans']='自$date1', ['zh-hant']='自$date1', default='<span style="color:red">from $date1</span>', }, ['until'] = { nDates = 1, ['be-tarask']={'усутыч да $date1', case={'gen',''}}, bn='$date1 পর্যন্ত', ckb='تاوەکوو $date1', cs={'do $date1', case={'gen', ''}}, de='bis $date1', en='until $date1', ['en-gb']='until $date1', ['en-ca']='until $date1', eo='ĝis $date1', es='hasta $date1', fr=function(date1, state) if state.precision > 10 then -- precision > month return "jusqu'au $date1" else return "jusqu'à $date1" end end, hr='do $date1', hsb={'do $date1', case={'gen',''}}, it='al $date1', nb='til $date1', nl='tot $date1', nn='til $date1', ml='$date1 വരെ', pl={'do $date1', case={'gen',''}}, ru={'вплоть до $date1', case={'gen',''}}, sk={'do $date1', case={'gen', ''}}, uk={'аж до $date1', case={'gen',''}}, ur='تک $date1', ['zh-hans']='直到$date1', ['zh-hant']='直到$date1', default='<span style="color:red">until $date1</span>', }, ['after'] = { nDates = 1, ar='بعد $date1', ['be-tarask']={'пасьля $date1', case={'gen',''}}, bn='$date1-এর পরে', ca='després de $date1', ckb='دوای $date1', cs={'po $date1', case={'loc',''}}, de='nach $date1', da='efter $date1', el='μετά από $date1', en='after $date1', ['en-gb']='after $date1', ['en-ca']='after $date1', eo='post $date1', es='después de $date1', et='pärast $date1', fa='پس از $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 jälkeen' end, case={'gen',''}}, fr='après $date1', gl='despois do $date1', he='לאחר $date1', hr='nakon $date1', hu='$date1 után', hsb={'po $date1', case={'loc',''}}, it='dopo il $date1', ja='$date1より後', mk='по $date1', ml='$date1-നു ശേഷം', nb='etter $date1', nn='etter $date1', nds='na $date1', nl='na $date1', no='etter $date1', pl={'po $date1', case={'loc',''}}, pt='após $date1', qu="$date1 p'unchaw qhipan", ro='după $date1', ru={'после $date1', case={'gen',''}}, sk={'po $date1', case={'loc',''}}, sl={function(date1, state) return 'po ' .. decYear(state,'sl','loc') .. ' $date1' end, case={'loc',''}}, sv='efter $date1', uk={'після $date1', case={'gen',''}}, ur='بعد $date1', vi='sau $date1', ['zh-hans']='在$date1之后', ['zh-hant']='在$date1之後', default='<span style="color:red">after $date1</span>', }, -- ========================================== -- === Adjectives =========================== -- ========================================== ['early'] = { nDates = 1, ar='بداية $date1', ['be-tarask']={'пачатак $date1', case={'gen',''}}, bn='$date1-এর শুরুতে', ca='al principi de $date1', ckb='سەرەتای $date1', cs={'začátek $date1', case={'gen',''}}, da='tidlig $date1', de='Anfang $date1', el='αρχές του $date1', en='early $date1', ['en-gb']='early $date1', ['en-ca']='early $date1', eo='komence de $date1', es='a principios de $date1', et='$date1 algus', fa='اوایل $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 alku' end, case={'gen',''}}, fr='début $date1', gl='a principios de $date1', he='תחילת $date1', hr='početak $date1', hsb={'spočatk $date1', case={'gen',''}}, hu='$date1 elején', it='inizio del $date1', ja='$date1初頭', mk='почеток на $date1', ml='$date1 ആദ്യപാദം', nb='tidlig $date1', nn='tidleg $date1', nds='Anfang $date1', nl='begin $date1', no='tidlig $date1', pl={'początek $date1', case={'gen',''}}, pt='a princípios de $date1', qu="$date1 ñawpaq p'unchawkunapi", ro='începând cu $date1', ru={'начало $date1', case={'gen',''}}, sk={'začiatok $date1', case={'gen',''}}, sl=function(date1, state)-- could actually be better should be "d'octobre à mars 2013' not "de octore 2013 à mars 2013" if state.precision == 9 then -- precision > month return 'zgodnje leto $date1' else -- "DE septebmbre" / "D'octobre" return 'zgodnji $date1' end end, sv='början av $date1', uk={'початок $date1', case={'gen',''}}, ur='ابتدا $date1', vi='đầu năm $date1', ['zh-hans']='$date1早期', ['zh-hant']='$date1早期', default='<span style="color:red">early $date1</span>', }, ['mid'] = { nDates = 1, ar='منتصف $date1', ['be-tarask']={'сярэдзіна $date1', case={'gen',''}}, bn='$date1-এর মাঝামাঝি', ca='a mitjan $date1', ckb='ناوەڕاستی $date1', cs={'polovina $date1', case={'gen',''}}, da='midten af $date1', de='Mitte $date1', el='μέσα του $date1', en='mid $date1', ['en-gb']='mid $date1', ['en-ca']='mid $date1', eo='meze de $date1', es='a mediados de $date1', et='$date1 keskel', fa='اواسط $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 puoliväli' end, case={'gen',''}}, fr='mi $date1', gl='a mediados de $date1', he='אמצע $date1', hr='sredina $date1', hsb={'srjedź $date1', case={'gen',''}}, hu='$date1 közepén', it='metà del $date1', ja='$date1半ば', mk='средина на $date1', ml='$date1 മദ്ധ്യം', nb='midten av $date1', nn='midten av $date1', nds='Midd $date1', nl='midden $date1', no='midten av $date1', pl={'środek $date1', case={'gen',''}}, pt='meados de $date1', qu="$date1 chawpin p'unchawkunapi", ro='mijlocul lui $date1', ru={'середина $date1', case={'gen',''}}, sk={'polovica $date1', case={'gen',''}}, sl={function(date1, state) return 'sredina ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='mitten av $date1', uk={'середина $date1', case={'gen',''}}, ur='وسط $date1', vi='giữa năm $date1', ['zh-hans']='$date1中期', ['zh-hant']='$date1中期', default='<span style="color:red">mid $date1</span>', }, ['late'] = { nDates = 1, ar='نهاية $date1', bn='$date1-এর শেষে', ['be-tarask']={'канец $date1', case={'gen',''}}, ca='al final de $date1', ckb='کۆتاییی $date1', cs='konec $date1', da='sen $date1', de='Ende $date1', el='τέλη του $date1', en='late $date1', ['en-gb']='late $date1', ['en-ca']='late $date1', eo='fine de $date1', es='a fines de $date1', et='$date1 lõpp', fa='اواخر $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 loppu' end, case={'gen',''}}, fr='fin $date1', gl='a finais de $date1', he='סוף $date1', hr='kraj $date1', hsb={'kónc $date1', case={'gen',''}}, hu='$date1 végén', it='fine del $date1', ja='$date1末', mk='крајот на $date1', ml='$date1 അന്ത്യപാദം', nb='sent $date1', no='seint $date1', nds='Enn $date1', nl='eind $date1', no='sent $date1', pl={'koniec $date1', case={'gen',''}}, pt='ao final de $date1', qu="$date1 qhipaq p'unchawkunapi", ro='sfârșitul lui $date1', ru={'конец $date1', case={'gen',''}}, sk={'koniec $date1', case={'gen',''}}, sl={function(date1, state) return 'pozno ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='slutet av $date1', uk={'кінець $date1', case={'gen',''}}, ur='اخیر $date1', vi='cuối năm $date1', ['zh-hans']='$date1晚期', ['zh-hant']='$date1晚期', default='<span style="color:red">late $date1</span>', }, ['circa'] = { nDates = 1, ar='حوالي $date1', ['be-tarask']={'каля $date1', case={'gen',''}}, bn='প্রায় $date1', ckb='بەنزیکی $date1', ca='cap a $date1', cs='cca $date1', da='ca. $date1', de='etwa $date1', el='περ. $date1', en='circa $date1', ['en-gb']='circa $date1', ['en-ca']='circa $date1', eo='ĉirkaŭ $date1', es='hacia $date1', -- suggestion use "hacia"/"hacia el" or leave the latin circa or ca. et='ca. $date1', fa='حوالی $date1', -- template:Circa used 'حدود $date1' fi={function(date1, state) return 'suunnilleen ' .. decYear(state,'fi','ine') .. '$date1' end, case={'ine',''}}, -- template:Circa used 'noin $date1' fr=function(date1, state) if state.precision >= 11 then -- precision >= day return 'vers le $date1' else return 'vers $date1' end end, gl='entre $date1', -- template:Circa used 'cara a $date1' he='בסביבות $date1', hr='oko $date1', hsb={'wokoło $date1', case={'gen',''}}, hu='$date1 körül', it='$date1 circa', ja='$date1頃', ko='$date1경', mk='~ $date1', -- template:Circa used 'o. $date1' ml= 'ഉദ്ദേശം $date1', nb='ca. $date1', nn='ca. $date1', nds='üm un bi $date1', nl='ca. $date1', no='ca. $date1', pl={'około $date1', case={'gen',''}}, pt='cerca de $date1', qu='$date1chá', ro='circa $date1', ru={'около $date1', case={'gen',''}}, sk='cca $date1', sl={function(date1, state) return 'ok. ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='c:a $date1', -- template:Circa used 'ca $date1' uk={'близько $date1', case={'gen', ''}}, ur='قریباً $date1', vi='vào khoảng $date1', ['zh-hans']='约$date1', ['zh-hant']='約$date1', default='<span style="color:red">circa $date1</span>', }, ['uncertain'] = { nDates = 1, ['zh-hans']='$date1(?)', ['zh-hant']='$date1(?)', default='$date1 (?)', }, probably = { -- see Q56644435 nDates = 1, ast = 'probablemente $date1', bn = 'সম্ভবত $date1', ca = 'probablement $date1', ckb = 'لەوانەیە $date1', cs = 'pravděpodobně $date1', de = 'wahrscheinlich $date1', el = 'πιθανόν $date1', en = 'probably $date1', ['en-gb']='probably $date1', ['en-ca']='probably $date1', eo = 'probable $date1', es = 'probable $date1', fa = 'احتمالاً $date1', fr = 'probablement $date1', he = 'כפי הנראה $date1', hu = 'feltehetőleg $date1', it = 'probabile $date1', mk = 'веројатно $date1', ml = 'ഒരുപക്ഷേ $date1', nl = 'waarschijnlijk $date1', pl = 'prawdopodobnie $date1', pt = 'provavelmente $date1', ru = 'вероятно $date1', sv = 'antagligen $date1', uk = 'ймовірно $date1', default='<span style="color:red">probably $date1</span>', }, possibly = { -- see Q30230067 nDates = 1, ast = 'posiblemente $date1', bn = 'যথাসম্ভব $date1', ckb = 'لەوانەیە $date1', cs = 'možná $date1', de = 'möglicherweise $date1', en = 'possibly $date1', ['en-gb']='possibly $date1', ['en-ca']='possibly $date1', eo = 'eble $date1', el = 'πιθανόν $date1', fa = 'احتمالاً $date1', fr = 'peut-être $date1', hu = 'talán $date1', mk = 'можеби $date1', ml = 'മിക്കവാറും $date1', nl = 'mogelijk $date1', pl = 'być może $date1', pt = 'possivelmente $date1', ro = 'poate $date1', ru = 'возможно $date1', sv = 'möjligen $date1', uk = 'можливо $date1', default='<span style="color:red">possibly $date1</span>', }, presumably = { -- see Q18122778 nDates = 1, ast = 'presumiblemente $date1', ckb = 'دەشێت $date1', de = 'vermutlich $date1', en = 'presumably $date1', ['en-gb']='presumably $date1', ['en-ca']='presumably $date1', fr = 'présumé $date1', it = 'presumibilmente $date1', ml = 'ഊഹപ്രകാരം $date1', ru = 'предположительно $date1', pl = 'przypuszczalnie $date1', uk = 'здогадно $date1', default='<span style="color:red">presumably $date1</span>', }, ['year unknown']= { nDates = 0, ca='Any desconegut', ['be-tarask']='Год невядомы', bn='অজানা বছর', ckb='ساڵی نەزاندراو', cs='neznámý rok', de='Jahr unbekannt', el='άγνωστο έτος', en='year unknown', ['en-gb']='year unknown', ['en-ca']='year unknown', eo='jaro nesciata', es='Año desconocido', fa='سال نامعلوم', fi='tuntematon vuosi', fr='Année inconnue', he='שנה לא ידועה', hsb='lěto njeznate', hr='nepoznata godina', it='anno sconosciuto', ja='年代不明', la='Sine anno', ml='വർഷം ലഭ്യമല്ല', mk='непозната година', nb='ukjent år', nn='ukjent år', nl='zonder jaar', no='ukjent år', pl='rok nieznany', qu='Mana riqsisqa wata', ru='Год неизвестен', sk='neznámy rok', sl='neznano leto', sv='Okänt år', uk='рік невідомий', ur='سال نامعلوم', vi='không rõ năm', ['zh-hans']='年份未知', ['zh-hant']='年份未知', default='<span style="color:red">year unknown</span>', }, -- ========================================== -- === Parts of a whole ===================== -- ========================================== ['firsthalf'] = { nDates = 1, ar='النصف الأول من $date1', ['be-tarask']={'першая палова $date1', case={'gen',''}}, bn='$date1-এর প্রথমার্ধ', ca='1a meitat de $date1', ckb='نیوەی یەکەمی $date1', cs={'1. polovina $date1', case={'gen',''}}, da='1. halvdel af $date1', de='1. Hälfte $date1', el='πρώτο μισό του $date1', en='first half of $date1', ['en-gb']='first half of $date1', ['en-ca']='first half of $date1', eo='unua duono de $date1', es='primera mitad de $date1', et='$date1 esimene pool', fa='نیمهٔ نخست $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 alkupuolisko' end, case={'gen',''}}, fr='1<sup>re</sup> moitié de $date1', gl='primeira metade de $date1', he='במחצית הראשונה של $date1', hsb={'prěnja połojca $date1', case={'gen',''}}, hu='$date1 első felében', hr='prva polovina $date1', it='prima metà del $date1', ja='$date1上半期', mk='прва половина на $date1', ml='$date1 ആദ്യപകുതി', nb='første halvdel av $date1', nn='fyrste halvdelen av $date1', nds='1. Helft $date1', nl='eerste helft $date1', no='første halvdel av $date1', pl={'pierwsza połowa $date1', case={'gen',''}}, pt='primeira metade de $date1', qu='$date1 ñawpaq kuskanpi', ro='prima jumătate a lui $date1', ru={'первая половина $date1', case={'gen',''}}, sk={'1. polovica $date1', case={'gen',''}}, sl={function(date1, state) return 'prva polovica ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='första halvan av $date1', uk={'перша половина $date1', case={'gen',''}}, ur='نصف اول $date1', vi='nửa đầu năm $date1', ['zh-hans']='$date1上半期', ['zh-hant']='$date1上半期', default='<span style="color:red">first half of $date1</span>', }, ['secondhalf'] = { nDates = 1, ar='النصف الثاني من $date1', ['be-tarask']={'другая палова $date1', case={'gen',''}}, bn='$date1-এর দ্বিতীয়ার্ধ', ca='2a meitat de $date1', ckb='نیوەی دووەمی $date1', cs={'2. polovina $date1', case={'gen',''}}, da='2. halvdel af $date1', de='2. Hälfte $date1', el='δεύτερο μισό του $date1', en='second half of $date1', ['en-gb']='second half of $date1', ['en-ca']='second half of $date1', eo='dua duono de $date1', es='segunda mitad de $date1', et='$date1 teine pool', fa='نیمهٔ دوم $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 loppupuolisko' end, case={'gen',''}}, fr='2<sup>e</sup> moitié de $date1', gl='segunda metade de $date1', he='במחצית השנייה של $date1', hsb={'druha połojca $date1', case={'gen',''}}, hr='druga polovina $date1', hu='$date1 második felében', it='seconda metà del $date1', ja='$date1下半期', mk='втора половина на $date1', ml='$date1 രണ്ടാം പകുതി', nb='andre halvdel av $date1', nn='andre halvdelen av $date1', nds='2. Helft vun $date1', nl='tweede helft $date1', no='andre halvdel av $date1', pl={'druga połowa $date1', case={'gen',''}}, pt='segunda metade de $date1', qu='$date1 qhipaq kuskanpi', ro='a doua jumătate a lui $date1', ru={'вторая половина $date1', case={'gen',''}}, sk={'2. polovica $date1', case={'gen',''}}, sl={'druga polovica {{IfNum | $date0 | leta}} $date1', case={'gen',''}}, sv='andra halvan av $date1', uk={'друга половина $date1', case={'gen',''}}, ur='نصف آخر $date1', vi='nửa cuối năm $date1', ['zh-hans']='$date1下半期', ['zh-hant']='$date1下半期', default='<span style="color:red">second half of $date1</span>', }, ['1quarter']= { nDates = 1, bn='$date1-এর প্রথম ত্রৈমাসিক', ['be-tarask']={'першая чвэрць $date1', case={'gen',''}}, ckb='چارەکی یەکەمی $date1', cs={'1. čtvrtina $date1', case={'gen',''}}, de='erstes Viertel $date1', en='first quarter of $date1', ['en-gb']='first quarter of $date1', ['en-ca']='first quarter of $date1', eo='unua kvarono de $date1', es='primer cuarto del $date1', fa='ربع نخست $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 ensimmäinen neljännes' end, case={'gen',''}}, fr= function(date1, state) return 'premier quart '.. frOf[state.precision] ..' $date1' end, he='רבעון ראשון של $date1', hsb={'prěnja štwórćina $date1', case={'gen',''}}, hr='prva četvrtina $date1', it='primo quarto del $date1', ja='$date1第1四半期', ml='$date1 ഒന്നാം ത്രൈമാസം', nb='første kvartal av $date1', nl='eerste kwart $date1', nn='fyrste kvartalet av $date1', no='første kvartal av $date1', pl={'pierwsza ćwiartka $date1', case={'gen',''}}, qu='$date1 ñawpaq ñiqin tawa phatmanpi', ru={'первая четверть $date1', case={'gen',''}}, sk={'1. štvrtina $date1', case={'gen',''}}, sl={'prva četrtina {{IfNum | $date0 | leta}} $date1', case={'gen',''}}, sl={function(date1, state) return 'prva četrtina ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='första kvartalet av $date1', uk={'1-ша чверть $date1', case={'gen',''}}, ur='پہلی سہ ماہی $date1', vi='quý 1 năm $date1', ['zh-hans']='$date1第一季度', ['zh-hant']='$date1第一季度', default='<span style="color:red">first quarter of $date1</span>', }, ['2quarter']= { nDates = 1, ['be-tarask']={'другая чвэрць $date1', case={'gen',''}}, bn='$date1-এর দ্বিতীয় ত্রৈমাসিক', ckb='چارەکی دووەمی $date1', cs={'2. čtvrtina $date1', case={'gen',''}}, de='zweites Viertel $date1', en='second quarter of $date1', ['en-gb']='second quarter of $date1', ['en-ca']='second quarter of $date1', eo='dua kvarono de $date1', es='segundo cuarto del $date1', fa='ربع دوم $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 toinen neljännes' end, case={'gen',''}}, fr= function(date1, state) return 'deuxième quart '.. frOf[state.precision] ..' $date1' end, he='רבעון שני של $date1', hsb={'druha štwórćina $date1', case={'gen',''}}, hr='druga četvrtina $date1', it='secondo quarto del $date1', ja='$date1第2四半期', ml='$date1 രണ്ടാം ത്രൈമാസം', nb='andre kvartal av $date1', nl='tweede kwart $date1', nn='andre kvartalet av $date1', no='andre kvartal av $date1', pl={'druga ćwiartka $date1', case={'gen',''}}, qu='$date1 iskay ñiqin tawa phatmanpi', ru={'вторая четверть $date1', case={'gen',''}}, sk={'2. štvrtina $date1', case={'gen',''}}, sl={function(date1, state) return 'druga četrtina ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='andra kvartalet av $date1', uk={'2-га чверть $date1', case={'gen',''}}, ur='دوسری سہ ماہی $date1', vi='quý 2 năm $date1', ['zh-hans']='$date1第二季度', ['zh-hant']='$date1第二季度', default='<span style="color:red">second quarter of $date1</span>', }, ['3quarter']= { nDates = 1, ['be-tarask']={'трэцяя чвэрць $date1', case={'gen',''}}, bn='$date1-এর তৃতীয় ত্রৈমাসিক', ckb='چارەکی سێیەمی $date1', cs={'3. čtvrtina $date1', case={'gen',''}}, de='drittes Viertel $date1', en='third quarter of $date1', ['en-gb']='third quarter of $date1', ['en-ca']='third quarter of $date1', eo='tria kvarono de $date1', es='tercer cuarto del $date1', fa='ربع سوم $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 kolmas neljännes' end, case={'gen',''}}, fr= function(date1, state) return 'troisième quart '.. frOf[state.precision] ..' $date1' end, he='רבעון שלישי של $date1', hsb={'třeća štwórćina $date1', case={'gen',''}}, hr='treća četvrtina $date1', it='terzo quarto del $date1', ja='$date1第3四半期', ml='$date1 മൂന്നാം ത്രൈമാസം', nb='tredje kvartal av $date1', nl='derde kwart $date1', nn='tredje kvartalet av $date1', no='tredje kvartal av $date1', pl={'trzecia ćwiartka $date1', case={'gen',''}}, qu='$date1 kimsa ñiqin tawa phatmanpi', sk={'3. štvrtina $date1', case={'gen',''}}, sl={function(date1, state) return 'tretja četrtina ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, ru={'третья четверть $date1', case={'gen',''}}, sv='tredje kvartalet av $date1', uk={'3-тя чверть $date1', case={'gen',''}}, ur='تیسری سہ ماہی $date1', vi='quý 3 năm $date1', ['zh-hans']='$date1第三季度', ['zh-hant']='$date1第三季度', default='<span style="color:red">third quarter of $date1</span>', }, ['4quarter']= { nDates = 1, ['be-tarask']={'чацьвёртая чвэрць $date1', case={'gen',''}}, bn='$date1-এর চতুর্থ ত্রৈমাসিক', ckb='چارەکی چوارەمی $date1', cs={'4. čtvrtina $date1', case={'gen',''}}, de='letztes Viertel $date1', en='fourth quarter of $date1', ['en-gb']='fourth quarter of $date1', ['en-ca']='fourth quarter of $date1', eo='kvara kvarono de $date1', es='último cuarto del $date1', fa='ربع چهارم $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 viimeinen neljännes' end, case={'gen',''}}, fr= function(date1, state) return 'quatrième quart '.. frOf[state.precision] ..' $date1' end, he='רבעון רביעי של $date1', hsb={'poslednja štwórćina $date1', case={'gen',''}}, hr='četvrta četvrtina $date1', it='quarto quarto del $date1', ja='$date1第4四半期', ml='$date1 നാലാം ത്രൈമാസം', nb='fjerde kvartal av $date1', nl='vierde kwart $date1', nn='tredje kvartalet av $date1', no='fjerde kvartal av $date1', pl={'czwarta ćwiartka $date1', case={'gen',''}}, qu='$date1 qhipaq ñiqin tawa phatmanpi', ru={'последняя четверть $date1', case={'gen',''}}, sk={'4. štvrtina $date1', case={'gen',''}}, sl={function(date1, state) return 'zadnja četrtina ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='fjärde kvartalet av $date1', uk={'4-та чверть $date1', case={'gen',''}}, ur='چوتھی سہ ماہی $date1', vi='quý 4 năm $date1', ['zh-hans']='$date1第四季度', ['zh-hant']='$date1第四季度', default='<span style="color:red">forth quarter of $date1</span>', }, -- ========================================== -- === Seasons ============================== -- ========================================== ['spring'] = { nDates = 1, ar='ربيع $date1', ['be-tarask']='вясна $date1', bn='বসন্তকাল $date1', ca='primavera $date1', ckb='بەھاری $date1', cs={'jaro $date1', case={'gen', ''}}, da='Forår $date1', de='Frühling $date1', en='Spring $date1', ['en-gb']='Spring $date1', ['en-ca']='Spring $date1', eo='printempo $date1', es='primavera de $date1', et='$date1 kevad', fa='بهار $date1', fi='Kevät $date1', fr='printemps $date1', gl='primavera $date1', he='אביב $date1', hr='proljeće $date1', hsb='nalěćo $date1', hu='$date1 tavasza', it='primavera $date1', ja='{{#if:$date1|$date1年}}春', mk='пролет $date1', ml='$date1 വസന്തം', nb='våren $date1', nds='Vörjohr $date1', nn='våren $date1', nl='voorjaar $date1', no='våren $date1', pl='wiosna $date1', pt='primavera de $date1', ro='primăvara lui $date1', ru='весна $date1', sk={'jar $date1', case={'gen', ''}}, sl='pomlad $date1', sv='våren $date1', uk='весна $date1', ur='بہار $date1', vi='mùa đông năm $date1', ['zh-hans']='{{#if:$date1|$date1年}}春季', ['zh-hant']='{{#if:$date1|$date1年}}春季', default='<span style="color:red">Spring $date1</span>', }, ['summer'] = { nDates = 1, ar='صيف $date1', ['be-tarask']='лета $date1', bn='গ্রীষ্মকাল $date1', ca='estiu $date1', ckb='ھاوینی $date1', cs={'léto $date1', case={'gen', ''}}, da='Sommer $date1', de='Sommer $date1', en='Summer $date1', ['en-gb']='Summer $date1', ['en-ca']='Summer $date1', eo='somero $date1', es='verano de $date1', et='$date1 suvi', fa='تابستان $date1', fi='Kesä $date1', fr='été $date1', gl='verán $date1', he='קיץ $date1', hr='ljeto $date1', hsb='lěćo $date1', hu='$date1 nyara', it='estate $date1', ja='{{#if:$date1|$date1年}}夏', mk='лето $date1', ml='$date1 വേനൽ', nb='sommeren $date1', nds='Sommer $date1', nn='sumaren $date1', nl='zomer $date1', no='sommeren $date1', pl='lato $date1', pt='verão de $date1', ro='vara lui $date1', ru='лето $date1', sk={'leto $date1', case={'gen', ''}}, sl='poletje $date1', sv='sommaren $date1', uk='літо $date1', ur='گرما $date1', vi='mùa hè năm $date1', ['zh-hans']='{{#if:$date1|$date1年}}夏季', ['zh-hant']='{{#if:$date1|$date1年}}夏季', default='<span style="color:red">Summer $date1</span>', }, ['autumn'] = { nDates = 1, ar='خريف $date1', ['be-tarask']='восень $date1', bn='শরত্কাল $date1', ca='tardor $date1', ckb='پاییزی $date1', cs={'podzim $date1', case={'gen', ''}}, da='Efterår $date1', de='Herbst $date1', en='Autumn $date1', ['en-gb']='Autumn $date1', ['en-ca']='Autumn $date1', eo='aŭtuno $date1', es='otoño de $date1', et='$date1 sügis', fa='پاییز $date1', fi='Syksy $date1', fr='automne $date1', gl='outono $date1', he='סתיו $date1', hr='jesen $date1', hsb='nazyma $date1', hu='$date1 ősze', it='autunno $date1', ja='{{#if:$date1|$date1年}}秋', mk='есен $date1', ml='$date1 ശരത്കാലം', nb='høsten $date1', nds='Harvst $date1', nn='hausten $date1', nl='herfst $date1', no='høsten $date1', pl='jesień $date1', pt='outono de $date1', ro='toamna lui $date1', ru='осень $date1', sk={'jeseň $date1', case={'gen', ''}}, sl='jesen $date1', sv='hösten $date1', uk='осінь $date1', ur='خزاں $date1', vi='mùa thu năm $date1', ['zh-hans']='{{#if:$date1|$date1年}}秋季', ['zh-hant']='{{#if:$date1|$date1年}}秋季', default='<span style="color:red">Autumn $date1</span>', }, ['winter'] = { nDates = 1, ar='شتاء $date1', ['be-tarask']='зіма $date1', bn='শীতকাল $date1', ca='hivern $date1', ckb='زستانی $date1', cs={'zima $date1', case={'gen', ''}}, da='Vinter $date1', de='Winter $date1', en='Winter $date1', ['en-gb']='Winter $date1', ['en-ca']='Winter $date1', eo='vintro $date1', es='invierno de $date1', et='$date1 talv', fa='زمستان $date1', fi='Talvi $date1', fr='hiver $date1', gl='inverno $date1', he='חורף $date1', hr='zima $date1', hsb='zyma $date1', hu='$date1 tele', it='inverno $date1', ja='{{#if:$date1|$date1年}}冬', mk='зима $date1', ml='$date1 ശൈത്യകാലം', nds='Winter $date1', nl='winter $date1', nb='vinteren $date1', nn='vinteren $date1', no='vinteren $date1', pl='zima $date1', pt='inverno de $date1', ro='iarna lui $date1', ru='зима $date1', sk={'zima $date1', case={'gen', ''}}, sl='zima $date1', sv='vintern $date1', uk='зима $date1', ur='سرما $date1', vi='mùa đông năm $date1', ['zh-hans']='{{#if:$date1|$date1年}}冬季', ['zh-hant']='{{#if:$date1|$date1年}}冬季', default='<span style="color:red">Winter $date1</span>', }, -- ========================================== -- === Decade/Century/Millennium ============ -- ========================================== ['decade'] = { nDates = 1, ar='أعوام $date1', ['be-tarask']='$date1-я гады', bn='$date1-এর দশক', ca='dècada del $date1', ckb='دەیەی $date1', cs=function(date1, state) return ((date1 % 100 == 0) and 'první dekáda' or ((date1 % 100) .. '. léta')) .. ' ' .. (math.floor(date1 / 100) + 1) .. '. století' end, da="$date1'erne", de='$date1er-Jahre', el='δεκαετία του $date1', en='$date1s', ['en-gb']='$date1s', ['en-ca']='$date1s', eo='$date1-aj jaroj', es='años $date1', et='$date1ndad', fa='دههٔ $date1', fi='$date1-luvulla', fr='années $date1', gl='anos $date1', he='שנות ה-$date1', hr='$date1-ih', hsb='$date1te lěta', hu='$date1-s évek', it="{{#ifexpr: $date1 mod 100 = 0 | primo decennio del {{Roman|{{#expr: ($date1 / 100) + 1}}}} sec. | anni '{{#expr: $date1 mod 100 }} del {{Roman|{{#expr: floor($date1 / 100) + 1}}}} sec.}}", ja='$date1年代', mk='$date1-ти', ml='ദശകം $date1-ൽ', nb='$date1-årene', nn='$date1-åra', nds='Johren $date1', nl='jaren $date1', no='$date1-årene', pl=function(date1, state) local str = 'lata '..(date1 % 100)..'. ' if (date1 % 100 == 0) then str = 'pierwsza dekada ' elseif (date1 % 100 == 10) then str = 'druga dekada ' end return str .. Roman(math.floor(date1 / 100) + 1) .. ' wieku' end, pt='década de $date1', qu='$date1 watakuna', ro='anii $date1', ru='$date1-е годы', sk=function(date1, state) return ((date1 % 100 == 0) and 'prvé desaťročie' or ((date1 % 100) .. '. roky')) .. ' ' .. (math.floor(date1 / 100) + 1) .. '. storočia' end, sl='$date1-ta leta', sv='$date1-talet{{#switch:$date1|1300|1400|1500|1600|1700|1800|1900|2000|2100=&nbsp;(decennium)}}', uk='$date1-ті роки', ur='دہائی $date1', vi='thập niên $date1', ['zh-hans']='$date1年代', ['zh-hant']='$date1年代', default='<span style="color:red">$date1s</span>', }, ['century'] = { nDates = 1, ar='القرن $date1', ['be-tarask']=function(date1, state) return Ordinal(date1, 'be-tarask') .. ' стагодзьдзе' end, bn=function(date1, state) return Ordinal(date1, 'bn') .. ' শতাব্দী' end, ca='segle {{small-caps|{{lc:{{Roman year|$date1}}}}}}', ckb='سەدەی $date1', cs='$date1. století', da='$date1. århundrede', de='$date1. Jahrhundert', el='$date1ος αιώνας', en=function(date1, state) return Ordinal(date1, 'en') .. ' century' end, ['en-gb']=function(date1, state) return Ordinal(date1, 'en-gb') .. ' century' end, ['en-ca']=function(date1, state) return Ordinal(date1, 'en-ca') .. ' century' end, eo='$date1-a jarcento', es=function(date1, state) return 'siglo ' .. Roman(date1) end, et='$date1. sajand', fa='سدهٔ $date1', fi='{{#expr: ($date1 - 1) * 100}}-luvulla', fr=function(date1, state) return Ordinal(date1, 'fr', 'roman', 'm') .. ' siècle' end, gl=function(date1, state) return 'século ' .. Roman(date1) end, he='המאה ה-$date1', hr='$date1. stoljeće', hsb='$date1. lětstotk', hu='$date1. évszázad', it=function(date1, state) return Roman(date1) .. ' sec.' end, ja='$date1世紀', la='saeculum $date1', mk=function(date1, state) return Roman(date1) .. ' век' end, ml='$date1-ആം നൂറ്റാണ്ട്', nb=function(date1, state) return Ordinal(date1, 'nb') .. ' århundre' end, nn=function(date1, state) return 'det ' .. Ordinal(date1, 'no') .. ' århundre' end, nds='$date1. Johrhunnert', nl='$date1e eeuw', no=function(date1, state) return Ordinal(date1, 'no') .. ' århundre' end, pl=function(date1, state) return Roman(date1) .. ' ' .. decCentury('pl',state.case) end, pt=function(date1, state) return 'século ' .. Roman(date1) end, qu='$date1 ñiqin pachakwata', ro='secolul al $date1-lea', ru=function(date1, state) return Roman(date1) .. ' век' end, sk='$date1. storočie', sl=function(date1, state) return Ordinal(date1, 'sl') .. '. stoletje' end, sv='{{#expr:$date1 - 1}}00-talet', tt=function(date1, state) return Roman(date1) .. ' гасыр' end, uk=function(date1, state) return Roman(date1) .. ' ст.' end, ur='صدی $date1', vi='thế kỷ $date1', ['zh-hans']='$date1世纪', ['zh-hant']='$date1世紀', default='<span style="color:red">$date1 century</span>', }, ['millennium'] = { nDates = 1, ['be-tarask']=function(date1, state) return Ordinal(date1, 'be-tarask') .. ' тысячагодзьдзе' end, bn=function(date1, state) return Ordinal(date1, 'bn') .. ' সহস্রাব্দ' end, ckb=function(date1, state) return 'ھەزارەی ' .. Ordinal(date1, 'ckb') end, cs='$date1. tisíciletí', de='$date1. Jahrtausend', en=function(date1, state) return Ordinal(date1, 'en') .. ' millennium' end, ['en-gb']=function(date1, state) return Ordinal(date1, 'en-gb') .. ' millennium' end, ['en-ca']=function(date1, state) return Ordinal(date1, 'en-ca') .. ' millennium' end, eo='$date1-a jarmilo', es=function(date1, state) return Roman(date1) .. ' milenio' end, fa=function(date1, state) return 'هزارهٔ ' .. Ordinal(date1, 'fa') end, fr=function(date1, state) return Ordinal(date1, 'fr', 'roman', 'm') .. ' millénaire' end, hr='$date1. tisućljeće', hsb='$date1. lěttysac', it=function(date1, state) return Roman(date1) .. ' millennio' end, ja='$date1千年紀', la='millennium $date1', ml='$date1-മത്തെ സഹസ്രാബ്ദം', nb=function(date1, state) return Ordinal(date1, 'nb') .. ' årtusen' end, nn=function(date1, state) return Ordinal(date1, 'no') .. ' tusenår' end, nl='$date1e millennium', no=function(date1, state) return Ordinal(date1, 'no') .. ' årtusen' end, pl=function(date1, state) return Roman(date1) .. ' ' .. decMillennium('pl',state.case) end, pt=function(date1, state) return Roman(date1) .. ' milénio' end, qu='$date1 ñiqin waranqawata', ru=function(date1, state) return Roman(date1) .. ' тысячелетие' end, sk='$date1. tisícročie', sl=function(date1, state) return Ordinal(date1, 'sl') .. '. tisočletje' end, sv=function(date1, state) return Ordinal(date1, 'sv') .. ' årtusendet' end, uk=function(date1, state) return Roman(date1) .. ' тис.' end, ur=function(date1, state) return 'ہزارہ ' .. Ordinal(date1, 'ur') end, vi='thiên niên kỷ $date1', ['zh-hans']=function(date1, state) return Ordinal(date1, 'zh-hans') .. '千年' end, ['zh-hant']=function(date1, state) return Ordinal(date1, 'zh-hant') .. '千年' end, --default='{{Please translate|$date1 millennium|Module:I18n/complex date}}', default='<span style="color:red">$date1 millennium</span>', }, ['million'] = { ckb = '$date1 میلیۆن ساڵ', de = '$date1 Millionen Jahre', en = '$date1 million years', ['en-gb'] = '$date1 million years', ['en-ca'] = '$date1 million years', eo = '$date1 milionoj da jaroj', es = '$date1 millones de años', nl = '$date1e miljoen jaar', fr = '$date1 millions d’années', it = '$date1 milioni di anni', ml = function(date1, state) if date1 >= 10 then return (date1 / 10) .. ' കോടി വർഷങ്ങൾ' -- 100 lakh = 1 crore else return (date1 * 10) .. ' ലക്ഷം വർഷങ്ങൾ' end end, nb = '$date1 millioner år', nl = '$date1 miljoen jaar', nn = '$date1 millionar år', pl = '$date1 milionów lat', pt = '$date1 milhões de anos', ru = '$date1 миллиона лет', uk = '$date1 мільйони років', ur = '$date1 ملین سال', ['zh-hans'] = '$date1百万年', ['zh-hant'] = '$date1百万年', default='<span style="color:red">$date1 million years</span>', nDates = 1, }, ['billion'] = { ckb = '$date1 بیلیۆن ساڵ', de = '$date1 Milliarden Jahre', en = '$date1 billion years', ['en-gb'] = '$date1 billion years', ['en-ca'] = '$date1 billion years', es = '$date1 mil millones de años', nl = '$date1e miljard jaar', fr = '$date1 milliard d’années', it = '$date1 miliardi di anni', ml = function(date1, state) return (date1 * 10) .. ' കോടി വർഷങ്ങൾ' end, nb = '$date1 milliarder år', nl = '$date1 miljard jaar', nn = '$date1 milliardar år', pl = '$date1 miliardów lat', pt = '$date1 mil milhões de anos', ru = '$date1 миллиардов лет', uk = '$date1 мільярдів років', ur = '$date1 بلین سال', ['zh-hans'] = '$date1十亿年', ['zh-hant'] = '$date1十億年', default='<span style="color:red$date1 billion years</span>', nDates = 1, }, -- ========================================== -- === Complex phrases ====================== -- ========================================== -- Phrases which for some languages can be build out of simpler phrases -- Their "default" state, used when no translation in the requested language was found, returns a "state" like -- table which is used to call the function again and built the phrase out of simple phrases -- $date1 - original date - 1 -- $date2 - original date -- if era is BC than $date1 and $date2 are swapped ['turn of the century'] = { nDates = 2, ['be-tarask']=function(date1, date2, state) return 'канец ' .. Roman(date1) ..' — пачатак ' .. Roman(date2) ..' стагодзьдзя' end, bn=function(date1, date2, state) return '$date1/' .. Ordinal(date2, 'bn') .. ' শতাব্দীর পালাবদলে' end, ckb=function(date1, date2, state) return 'سەردەمی $date1/' .. Ordinal(date2, 'ckb') .. ' سەدە' end, cs='přelom $date1. a $date2. století', de=function(date1, date2, state) return 'Jahrhundertwende $date1/' .. Ordinal(date2, 'de') end, en=function(date1, date2, state) return 'turn of the $date1/' .. Ordinal(date2, 'en') .. ' century' end, ['en-gb']=function(date1, date2, state) return 'turn of the $date1/' .. Ordinal(date2, 'en-gb') .. ' century' end, ['en-ca']=function(date1, date2, state) return 'turn of the $date1/' .. Ordinal(date2, 'en-ca') .. ' century' end, eo='ŝanĝo de la $date1-a al la $date2-a jarcentoj', es=function(date1, date2, state) return 'hacia finales del siglo ' .. Roman(date1) .. ' y principios del siglo ' .. Roman(date2) .. '' end, fi='{{#expr: $date1 * 100-100}}-luvun loppu ja {{#expr: $date2 * 100-100}}-luvun alku', fr=function(date1, date2, state) return 'fin du ' .. Ordinal(date1, 'fr', 'roman', 'm') .. ' siècle / début du ' .. Ordinal(date2, 'fr', 'roman', 'm') end, hr='kraj $date1. i početak $date2. stoljeća', hsb=function(date1, date2, state) return 'přechod ' .. Roman(date1) ..' do ' .. Roman(date2) ..' lětstotka' end, it=function(date1, date2, state) return 'fine del ' .. Roman(date1) .. ' sec. e inizio del ' .. Roman(date2) .. ' sec.' end, ja='$date1世紀から$date2世紀への変わり目', ml='$date1 നൂറ്റാണ്ട് അവസാനവും $date2 നൂറ്റാണ്ട് ആദ്യവും', nb=function(date1, date2, state) return 'slutten av det ' .. Ordinal(date1, 'nb') .. ' århundre/begynnelsen av det ' .. Ordinal(date2, 'nb') .. ' århundre' end, nl=function(date1, date2, state) return 'eind $date1e/begin $date2e eeuw' end, nn=function(date1, date2, state) return 'slutten av det ' .. Ordinal(date1, 'nn') .. ' hundreåret/byrjinga av det ' .. Ordinal(date2, 'nn') .. ' hundreåret' end, pl=function(date1, date2, state) return 'przełom ' .. Roman(date1) ..' i ' .. Roman(date2) ..' wieku' end, qu="$date1 ñiqin waranqawatamanta $date2 ñiqin waranqawataman t'ikrasqanpi", ru=function(date1, date2, state) return 'конец ' .. Roman(date1) ..' — начало ' .. Roman(date2) ..' века' end, sk='prelom $date1. a $date2. storočia', sl=function(date1, date2, state) return 'pozno ' .. Ordinal(date1, 'sl') .. '. in zgodnje ' .. Ordinal(date2, 'sl') .. '. stoletje' end, sv=function(date1, date2, state) return '$date1/' .. Ordinal(date2, 'sv') .. ' årtusendets sekelskifte' end, uk=function(date1, date2, state) return 'кін. ' .. Roman(date1) ..' ст. — поч. ' .. Roman(date2) ..' ст.' end, vi='bước sang thế kỷ $date2', ['zh-hans']=function(date1, date2, state) return '$date1世纪末' .. Ordinal(date2, 'zh-hans') .. '世纪初' end, ['zh-hant']=function(date1, date2, state) return '$date1世紀末' .. Ordinal(date2, 'zh-hant') .. '世紀初' end, default=function(date1, date2, state) return {conj='and', adj1 = 'late', unit1='century', era1=state.era1, adj2 = 'early', unit2='century', era2=state.era2} end, }, -- $date1 - original date - 10 -- $date2 - original date -- if era is BC than $date1 and $date2 are swapped ['turn of the decade']={ nDates = 2, ['be-tarask']='канец $date1-х — пачатак $date2-х гг.', bn='$date1-এর দশকের শেষে এবং $date2-এর দশকের শুরুতে', cs=function(date1, date2, state) local decade1 = math.floor((date1 % 100) / 10) local decade2 = math.floor((date2 % 100) / 10) local century1 = math.floor( date1 / 100) + 1 local century2 = math.floor( date2 / 100) + 1 if decade1 == 0 and date1 < date2 then return "přelom 1. a 2. dekády " .. century1 .. ". století" elseif decade2 == 0 and date2 < date1 then return "přelom 9. a 10. dekády " .. century1 .. ". století" elseif (decade1 == 9 and date1 < date2) or (decade2 == 9 and date2 < date1) then return "přelom " .. century1 .. ". a " .. century2 .. ". století" else return "přelom " .. decade1 .. "0. a " .. decade2 .. "0. let " .. century1 .. ". století" end end, de='späte $date1er und frühe $date2er', ckb='کۆتایییەکانی $date1 و سەرەتاکانی $date2', en='late $date1s and early $date2s', ['en-gb']='late $date1s and early $date2s', ['en-ca']='late $date1s and early $date2s', eo='ŝanĝo de la $date1-a al la $date2-a jardekoj', es='finales de los años $date1 y principios de los años $date2', fa='اواخر دههٔ $date1 و اوایل دههٔ $date2', fi='$date1-luvun loppu ja $date2-luvun alku', fr='fin des années $date1 / début des années $date2', hr='kraj $date1-ih i početak $date2-ih', hsb='pózdnje $date1te a zažne $date2te lěta', it='fine degli anni $date1 / inizio degli anni $date2', ja='$date1年代末から$date2年代初頭', ml='$date1കൾ അവസാനവും $date2കൾ ആദ്യവും', nb = 'sent på $date1-tallet og tidlig på $date2-tallet', nl = 'eind $date1e/begin $date2e decennium', nn = 'seint på $date1-talet og tidleg på $date2-talet', pl=function(date1, date2, state) local decade1 = math.floor(date1 % 100) local decade2 = math.floor(date2 % 100) local century1 = Roman(math.floor( date1 / 100) + 1) local century2 = Roman(math.floor( date2 / 100) + 1) if decade1 == 0 and date1 < date2 then return "przełom pierwszej i drugiej dekady " .. century1 .. " wieku" elseif decade1 == 10 and date1 < date2 then return "przełom drugiej dekady i lat 20. " .. century1 .. " wieku" elseif decade1 == 90 and decade2 == 0 then return "przełom ostatniej dekady " .. century1 .. " i pierwszej dekady " .. century2 .. " wieku" else return "przełom lat " .. decade1 .. ". i " .. decade2 .. ". " .. century1 .. " wieku" end end, qu='qhipaq $date1 watakuna ñawpaq $date2 watakunapas', ru='конец $date1-х — начало $date2-х гг.', sv='sena $date1-talet och tidiga $date2-talet', sk=function(date1, date2, state) local decade1 = math.floor((date1 % 100) / 10) local decade2 = math.floor((date2 % 100) / 10) local century1 = math.floor( date1 / 100) + 1 local century2 = math.floor( date2 / 100) + 1 if decade1 == 0 and date1 < date2 then return "prelom 1. a 2. desaťročia " .. century1 .. ". storočia" elseif decade2 == 0 and date2 < date1 then return "prelom 9. a 10. desaťročia " .. century1 .. ". storočia" elseif (decade1 == 9 and date1 < date2) or (decade2 == 9 and date2 < date1) then return "prelom " .. century1 .. ". a " .. century2 .. ". storočia" else return "prelom " .. decade1 .. "0. a " .. decade2 .. "0. rokov " .. century1 .. ". storočia" end end, sl='pozna $date1-ta in zgodnja $date2-ta leta', uk='кінець $date1-х — початок $date2-х рр.', ur='اخیر دہائی $date1 و ابتدا دہائی $date2', vi='bước sang thập niên $date2', ['zh-hans']='$date1年代末$date2年代初', ['zh-hant']='$date1年代末$date2年代初', default=function(date1, date2, state) return {conj='and', adj1 = 'late', unit1='decade', era1=state.era1, adj2 = 'early', unit2='decade', era2=state.era2} end, }, -- $date1 - original date - 1 -- $date2 - original date -- if era is BC than $date1 and $date2 are swapped ['turn of the year']={ nDates = 2, ['be-tarask']='канец $date1 — пачатак $date2 г.', bn='$date1-এর শেষে এবং $date2-এর শুরুতে', ckb='کۆتاییی $date1 و سەرەتای $date2', cs='přelom roku $date1/$date2', de='Jahreswechsel $date1/$date2', en='late $date1 and early $date2', ['en-gb']='late $date1 and early $date2', ['en-ca']='late $date1 and early $date2', eo='jarŝanĝo $date1 / $date2', es='finales de $date1 y principios de $date2', fa='اواخر $date1 و اوایل $date2', fi='vuoden $date1 loppu ja vuoden $date2 alku', fr='fin $date1 / début $date2', hsb='kónc $date1/spočatk $date2', ja='$date1年末から$date2年初め', ml='$date1 അവസാനവും $date2 ആദ്യവും', nb='årsskiftet $date1/$date2', nl='eind $date1 en begin $date2', nn='årsskiftet $date1/$date2', pl='koniec $date1 i początek $date2 roku', qu="$date1 qhipaq p'unchawkuna $date2 ñawpaq p'unchawkunapas", ru='конец $date1 — начало $date2 г.', sk='prelom rokov $date1/$date2', sl='konec leta $date1 in začetek $date2', sv='sena $date1 och tidiga $date2', uk='кінець $date1 — початок $date2 р.', ur='اخیر $date1 و ابتدا $date2', vi='bước sang năm $date2', ['zh-hans']='$date1年末$date2年初', ['zh-hant']='$date1年末$date2年初', default=function(date1, date2, state) return {conj='and', adj1 = 'late', unit1='year', era1=state.era1, adj2 = 'early', unit2='year', era2=state.era2} end, }, ['circa2'] = { nDates = 2, ar='حوالي $date1 – $date2', ['be-tarask']={'прыкладна з $date1 да прыкладна $date2', case={'gen','gen'}}, bn='প্রায় $date1 এবং প্রায় $date2-এর মাঝামাঝি', ca='cap a $date1 o $date2', ckb='بە نزیکی لەنێوان $date1 و $date2', cs='cca $date1 až $date2', da='ca. mellem $date1 og $date2', de='etwa $date1 bis $date2', el='περίπου μεταξύ $date1 και $date2', en='between circa $date1 and circa $date2', ['en-gb']='between circa $date1 and circa $date2', ['en-ca']='between circa $date1 and circa $date2', eo='proksimume de $date1 ĝis $date2', es='hacia $date1 o $date2', et='ca. $date1 ja $date2 vahel', fa='حدوداً بین $date1 و $date2', fi={function(date1, date2, state) return 'suunnilleen ' .. decYear(state,'fi','xxx') .. '$date1 ja $date2 välisenä aikana' end, case={'gen','gen'}}, fr='vers $date1-$date2', gl='entre $date1 e $date2', he='בסביבות ה- $date1 עד $date2', hr='oko između $date1 i $date2', hsb={'wot něhdźe $date1 do něhdźe $date2', case={'gen','gen'}}, hu='$date1 és $date2 között', it='tra il $date1 e il $date2 circa', ja='$date1頃と$date2頃の間', mk='~ помеѓу $date1 и $date2', ml='ഉദ്ദേശം $date1-നും ഉദ്ദേശം $date2-നും ഇടയ്ക്ക്', nb='ca. mellom $date1 og $date2', nn='ca. mellom $date1 og $date2', nds='üm un bi $date1 bet $date2', nl='ca. $date1 tot $date2', no='ca. mellom $date1 og $date2', pl={'od około $date1 do około $date2', case={'gen','gen'}}, pt='cerca de $date1 a $date2', qu="$date1 p'unchawmantachá $date2 p'unchawkamachá", ro='circa $date1 – $date2', ru={'с около $date1 до, примерно, $date2', case={'gen','gen'}}, sk='cca $date1 až $date2', sl={function(date1, date2, state) return 'ok. ' .. decYear(state,'sl','gen') .. ' $date1 do $date2' end, case={'gen','gen'}}, sv='c:a $date1 till $date2', uk={'з близько $date1 до, приблизно, $date2', case={'gen','gen'}}, ur='قریباً بین $date1 و $date2', vi='giữa vào khoảng $date1 và vào khoảng $date2', ['zh-hans']='约$date1~$date2间', ['zh-hant']='約$date1~$date2間', default=function(date1, date2, state) return {conj='between', adj1 = 'circa', unit1=state.unit1, era1=state.era1, adj2 = 'circa', unit2=state.unit2, era2=state.era2} end, }, -- ========================================== -- === Eras and calendars =================== -- ========================================== ['julian'] = { nDates = 2, ar='$date1 ($date2 حسب [[w:ar:التقويم اليولياني|تقويم يولياني]])', ['be-tarask']='$date1 ($date2 паводле [[w:be-tarask:Юліянскі каляндар|ст. ст.]]', bn='$date1 (জুলিয়ান ক্যালেন্ডারে $date2)', ca='$date1 ($date2 segons el [[w:ca:Calendari julià|calendari julià]])', ckb='$date1 ($date2 بەپێی [[w:ڕۆژژمێری جولیانی|ڕۆژژمێری جولیانی]])', cs='$date1 ($date2 podle [[w:cs:Juliánský kalendář|juliánského kalendáře]])', da='$date1 ($date2 [[w:da:Julianske kalender|Julianske kalender]])', de='$date1 ($date2 [[w:de:Julianischer Kalender|Julianischer Kalender]])', el='$date1 ($date2 με το [[w:el:Ιουλιανό ημερολόγιο|Ιουλιανό ημερολόγιο]])', en='$date1 ($date2 in [[w:Old Style and New Style dates|Julian calendar]])', ['en-gb']='$date1 ($date2 in [[w:Old Style and New Style dates|Julian calendar]])', ['en-ca']='$date1 ($date2 in [[w:Old Style and New Style dates|Julian calendar]])', fa='$date1 ($date2 در [[w:fa:Old Style and New Style dates|تقویم جولیان]])', eo='$date1 ($date2 laŭ la [[w:eo:Julia kalendaro|Julia Kalendaro]])', es='$date1 ($date2 según el [[w:es:Calendario juliano|calendario juliano]])', et='$date1 ($date2 [[w:et:Juliuse kalender|Juliuse kalendris]])', fi='$date1 ($date2 [[w:fi:juliaaninen kalenteri|juliaanisen kalenterin]] mukaan)', fr='$date1 ($date2 dans le [[w:fr:calendrier julien|calendrier julien]])', gl='$date1 ($date2 segundo o [[w:gl:Calendario xuliano|calendario xuliano]])', hu='$date1 ($date2 a [[w:hu:Julianus-naptár|Julián naptárban]])', hr='$date1 ($date2 prema [[w:hr:Julijanski kalendar|julijanskom kalendaru]])', he='$date1 ($date2 ב[[w:he:לוח היוליאני|הלוח היוליאני]])', hsb='$date1 ($date2 po julianiskej protyce)', it='$date1 ($date2 secondo il [[w:it:Calendario giuliano|calendario giuliano]])', ja='$date1([[w:ja:ユリウス暦|ユリウス暦]] $date2)', mk='$date1 ($date2 по [[w:mk:Јулијански календар|Јулијанскиот календар]])', ml='$date1 ([[w:ml:ജൂലിയൻ കാലഗണനാരീതി|ജൂലിയൻ വർഷത്തിൽ]] $date2)', ml='$date1 ([[w:Old Style and New Style dates|ജൂലിയൻ കാലഗണനാരീതിയിൽ]] $date2)', nds='$date1 ($date2 na’n [[w:nds:Juliaansch Klenner|Juliaanschen Klenner]])', nb='$date1 ($date2 etter [[w:no:Juliansk kalender|den julianske kalenderen]])', nn='$date1 ($date2 etter [[w:no:Juliansk kalender|den julianske kalenderen]])', nl='$date1 ($date2 volgens de [[w:nl:Juliaanse kalender|Juliaanse kalender]])', no='$date1 ($date2 etter [[w:no:Juliansk kalender|den julianske kalender]])', pl='$date1 ($date2 wedle [[w:pl:Daty nowego i starego porządku|kalendarza juliańskiego]])', pt='$date1 ($date2 no [[w:pt:Mudança para o calendário gregoriano|calendário juliano]])', qu='$date1 ($date2 [[w:qu:Hulyanu kalindaryu|Hulyanu kalindaryupi]])', ro='$date1 ($date2 în [[w:ro:calendarul iulian|calendarul iulian]])', ru='$date1 ($date2 [[w:ru:Юлианский календарь|по ст. ст.]])', sk='$date1 ($date2 podľa [[w:sk:Juliánsky kalendár|juliánskeho kalendára]])', sl='$date1 ($date2 po [[w:sl:Julijanski koledar|julijanskem koledarju]])', sv='$date1 ($date2 [[w:sv:Gamla stilen|gamla stilen]])', uk='$date1 ($date2 [[w:uk:Юліанський календар|Юліанський календар]])', ur='$date1 ($date2 حسب [[w:ur:نئے اور پانے انداز کی تاریخ|جولین تقویم]])', vi='$date1 ($date2 trong [[w:vi:Lịch Julius|lịch Juliêng]])', ['zh-hans']='$date1([[w:Old Style and New Style dates|格里历]]$date2)', ['zh-hant']='$date1([[w:Old Style and New Style dates|格里曆]]$date2)', default='<span style="color:red">$date1 ($date2 in [[w:Old Style and New Style dates|Julian calendar]])</span>', }, ['ad'] = { nDates = 1, ['be-tarask']='$date1 [[w:be-tarask:ад Нараджэньня Хрыста|пасьля Н. Х.]]', bn ='$date1 খ্রিস্টাব্দ', ca ='$date1 dC', ckb ='$date1 [[w:ckb:ئانۆ دۆمینی|پاز]]', cs ='$date1 n. l.', de ='$date1 n. Chr.', el ='$date1 μ.Χ.', en ='$date1 [[w:en:Anno Domini|AD]]', ['en-gb'] ='$date1 [[w:en:Anno Domini|AD]]', ['en-ca'] ='$date1 [[w:en:Anno Domini|AD]]', eo ='$date1 pK', es ='$date1 [[w:es:Anno Dómini|d. C.]]', fa ='$date1 م.', fi ='$date1 jaa.', fr ='$date1 ap. J.-C.', gl ='$date1 d.C.', he ='$date1 לספירה', hu ='i. sz. $date1', hr ='$date1 p. Kr.', hsb='$date1 po Chr.', it ='$date1 d.C.', ja ='西暦$date1', mk ='$date1 н.е.', ml ='ക്രി.വ. $date1', nb ='$date1 e.Kr.', nl ='$date1 n.Chr.', nn ='$date1 e.Kr.', no ='$date1 e.Kr.', pl ='$date1 [[w:pl:naszej ery|n.e.]]', ro ='$date1 d.Hr.', ru ='$date1 н. э.', sk ='$date1 po Kr.', sl ='$date1 n. št.', sv ='$date1 [[w:sv:Efter Kristus|e.Kr.]]', uk ='$date1 н. е.', ur ='$date1 ء', vi ='$date1 [[w:vi:Công Nguyên|CN]]', ['zh-hans'] ='[[w:zh:基督纪年|公元]]$date1', ['zh-hant'] ='[[w:zh:基督纪年|公元]]$date1', default='<span style="color:red">$date1 [[w:en:Anno Domini|AD]]</span>', }, ['ah'] = { nDates = 1, ['be-tarask']='$date1 [[w:be-tarask:Ісламскі каляндар|хіджры]]', bn ='$date1 [[w:bn:ইসলামি বর্ষপঞ্জি|হিজরী]]', ckb ='$date1 [[w:ckb:ساڵی کۆچی|سک]]', cs ='$date1 [[w:cs:Anno Hegirae|AH]]', en ='$date1 [[w:en:Hijri year|AH]]', ['en-gb'] ='$date1 [[w:en:Hijri year|AH]]', ['en-ca'] ='$date1 [[w:en:Hijri year|AH]]', eo ='$date1 post la [[w:eo:Heĝiro|Heĝiro]]', fa ='$date1 ه.ق.', fr ='$date1 [[w:fr:Calendrier musulman|A.H.]]', he ="$date1 [[w:he:הלוח המוסלמי|להג'רה]]", hr ='$date1 god. hidžre', ja ='[[w:ja:ヒジュラ暦|ヒジュラ暦]]$date1', ml ='$date1 [[w:ml:ഇസ്‌ലാമിക കലണ്ടർ|ഹിജ്റ]]', nb ='$date1 [[w:no:Den muslimske kalenderen|A.H.]]', nn ='$date1 [[w:nn:Den muslimske kalenderen|A.H.]]', pl ='$date1 [[w:pl:Kalendarz muzułmański|A.H.]]', ru ='$date1 [[w:ru:Исламский календарь|хиджры]]', sk ='$date1 [[w:sk:A. H.|A. H.]]', sv ='$date1 [[w:sv:Muslimska kalendern|A.H.]]', uk ='$date1 [[w:uk:Мусульманський календар|хіджри]]', ur ='$date1 ھ', vi ='$date1 [[w:vi:Lịch Hồi giáo|AH]]', ['zh-hans'] ='[[w:en:Hijri year|Hijri历]]$date1', ['zh-hant'] ='[[w:en:Hijri year|Hijri曆]]$date1', default='<span style="color:red">$date1 [[w:en:Hijri year|AH]]</span>', }, ['bc'] = { nDates = 1, ar ='$date1 ق.م.', ['be-tarask']='$date1 [[w:be-tarask:да Нараджэньня Хрыста|да Н. Х.]]', bn ='খ্রিস্টপূর্ব $date1', ca ='$date1 [[w:ca:Abans de Crist|aC]]', ckb ='$date1 [[w:ckb:پێش زایین|پز]]', cs ='$date1 [[w:cs:Př. n. l.|př. n. l.]]', de ='$date1 [[w:de:v. Chr.|v. Chr.]]', el ='$date1 π.Χ.', en ='$date1 [[w:en:Ante Christum Natum|BC]]', ['en-gb'] ='$date1 [[w:en:Ante Christum Natum|BC]]', ['en-ca'] ='$date1 [[w:en:Ante Christum Natum|BC]]', eo ='$date1 aK', es ='$date1 [[w:es:Antes de Cristo|a. C.]]', fa ='$date1 پ.م.', fi ='$date1 eaa.', fr ='$date1 av. J.-C.', gl ='$date1 [[w:gl:Antes de Cristo|a.C.]]', he ='$date1 לפני הספירה', hr ='$date1 [[w:hr:Pr. Kr.|pr. Kr.]]', hsb='$date1 do Chr.', hu ='i. e. $date1', it ='$date1 [[w:it:Avanti Cristo|a.C.]]', ja ='[[w:ja:紀元前|紀元前]]$date1', mk ='$date1 п.н.е.', ml ='ക്രി.മു. $date1', nds='$date1 vör uns Tied', nb ='$date1 f.Kr.', nl ='$date1 v.Chr.', nn ='$date1 f.Kr.', no ='$date1 f.Kr.', pl ='$date1 [[w:pl:przed naszą erą|p.n.e.]]', ro ='$date1 î.Hr.', ru ='$date1 до н. э.', sk ='$date1 [[w:sk:Pred Kristom|pred Kr.]]', sl ='$date1 pr. n. št.', sv ='$date1 [[w:sv:Före Kristus|f.Kr.]]', uk ='$date1 до н. е.', ur ='$date1 ق م', vi ='$date1 [[w:vi:Trước Công Nguyên|TCN]]', ['zh-hans'] ='[[w:zh:基督纪年|公元前]]$date1', ['zh-hant'] ='[[w:zh:基督纪年|公元前]]$date1', default='<span style="color:red">$date1 [[w:en:Ante Christum Natum|BC]]</span>', }, ['bp'] = { nDates = 1, ar ='$date1 [[w:ar:قبل الحاضر|قبل الحاضر]]', be ='$date1 [[w:be-tarask:Да нашых дзён|Да нашых дзён]]', ['be-tarask']='$date1 [[w:be-tarask:Да нашых дзён|Да нашых дзён]]', bn ='বর্তমানের আগে $date1', ca ='$date1 [[w:ca:BP (unitat de temps)|BP]]', ckb ='$date1 [[w:ckb:پێش ئێستا|پێش ئێستا]]', cs ='$date1 [[w:cs:B. P.|B. P.]]', de ='$date1 [[w:de:Before Present|BP]]', el ='$date1 [[w:en:Before Present|BP]]', en ='$date1 [[w:en:Before Present|BP]]', ['en-gb'] ='$date1 [[w:en:Before Present|BP]]', ['en-ca'] ='$date1 [[w:en:Before Present|BP]]', eo='$date1 [[w:eo:Before Present|BP]]', es ='$date1 [[w:es:Before Present|BP]]', et ='$date1 [[w:et:Before Present|BP]]', fa ='$date1 پیش از اکنون', fi ='$date1 [[w:fi:BP (ajanlasku)|BP]]', fr ='$date1 [[w:fr:Avant le présent|avant le présent]]', hr ='$date1 [[w:hr:BP|prije današnjice]]', it ='$date1 [[w:it:Before Present|BP]]', ja ='$date1 [[w:ja:BP (年代測定)|BP]]', lv ='$date1 [[w:lv:Pirms mūsdienām|Pirms mūsdienām]]', mk ='$date1 [[w:mk:Пред сегашноста|п.с.]]', ml ='[[w:en:Before Present|ഇന്നത്തെ കാലത്തിനു മുമ്പ്]] $date1', nb ='$date1 [[w:no:Before Present|BP]]', nn ='$date1 [[w:nn:Before Present|BP]]', nl ='$date1 [[w:nl:Before Present|BP]]', no ='$date1 [[w:no:Before Present|BP]]', pl ='$date1 [[w:pl:Before Present|BP]]', pt ='$date1 [[w:pt:Antes do Presente|Antes do Presente]]', ru ='$date1 [[w:ru:До настоящего времени|до н. в.]]', sk ='$date1 [[w:sk:BP (čas)|BP]]', sl ='$date1 [[w:sl:pred sedanjostjo|p. s.]]', sr ='$date1 [[w:sr:Прије садашњости|Прије садашњости]]', sv ='$date1 [[w:sv:B.P.|B.P.]]', uk ='$date1 [[w:uk:Років тому|Років тому]]', ur ='$date1 قبل حاضر', vi ='$date1 [[w:en:Before Present|trước ngày nay]]', ['zh-hans'] ='[[w:zh:距今|距今]]$date1', ['zh-hant'] ='[[w:zh:距今|距今]]$date1', default='<span style="color:red">$date1 [[w:en:Before Present|BP]]</span>', }, } -- List of possible string inputs to "adj", "junction", "era", and "precision" fields -- Each term is matched to its synonym and the role: a - adjective, e - era -- p - precision, c - calendar and combos, r - certainty and j - conjunction p.Synonyms = { -- adjectives and prepositions ['quarter1']={'1quarter','a'}, ['1stquarter']={'1quarter','a'}, ['1st quarter']={'1quarter','a'}, ['first quarter']={'1quarter','a'}, ['1quarter']={'1quarter','a'}, ['quarter2']={'2quarter','a'}, ['2ndquarter']={'2quarter','a'}, ['2nd quarter']={'2quarter','a'}, ['second quarter']={'2quarter','a'}, ['2quarter']={'2quarter','a'}, ['quarter3']={'3quarter','a'}, ['3rdquarter']={'3quarter','a'}, ['3rd quarter']={'3quarter','a'}, ['third quarter']={'3quarter','a'}, ['3quarter']={'3quarter','a'}, ['quarter4']={'4quarter','a'}, ['4thquarter']={'4quarter','a'}, ['4th quarter']={'4quarter','a'}, ['fourth quarter']={'4quarter','a'}, ['4quarter']={'4quarter','a'}, ['spring']={'spring','a'}, ['summer']={'summer','a'}, ['fall']={'autumn','a'}, ['autumn']={'autumn','a'}, ['winter']={'winter','a'}, ['beginning']={'early','a'}, ['early']={'early','a'}, ['1half']={'firsthalf','a'}, ['1sthalf']={'firsthalf','a'}, ['1st half']={'firsthalf','a'}, ['firsthalf']={'firsthalf','a'}, ['first half']={'firsthalf','a'}, ['end']={'late','a'}, ['late']={'late','a'}, ['middle']={'mid','a'}, ['mid']={'mid','a'}, ['2half']={'secondhalf','a'}, ['2ndhalf']={'secondhalf','a'}, ['2nd half']={'secondhalf','a'}, ['secondhalf']={'secondhalf','a'}, ['second half']={'secondhalf','a'}, ['<']={'before','a'}, ['b']={'before','a'}, ['before']={'before','a'}, ['>']={'after','a'}, ['a']={'after','a'}, ['after']={'after','a'}, ['~']={'circa','a'}, ['c']={'circa','a'}, ['ca']={'circa','a'}, ['circa']={'circa','a'}, ['by']={'by','a'}, ['from']={'from','a'}, ['until']={'until','a'}, ['uncertain']={'uncertain','r'}, ['possibly']={'possibly','r'}, ['probably']={'probably','r'}, ['presumably']={'presumably','r'}, -- eras ['bc']={'bc','e'}, ['bce']={'bc','e'}, ['before present']={'bp','e'}, ['bp']={'bp','e'}, ['ce']={'ad','e'}, ['ad']={'ad','e'}, ['ah']={'ah','e'}, -- conjunctions ['&']={'and','j'}, ['and']={'and','j'}, ['or']={'or','j'}, ['-']={'from-until','j'}, ['–']={'from-until','j'}, ['from-until']={'from-until','j'}, ['between']={'between','j'}, ['circa2']={'circa2','a'}, -- precision ['day']={'day','p'}, ['month']={'month','p'}, ['year']={'year','p'}, ['s']={'decade','p'}, ['decade']={'decade','p'}, ['century']={'century','p'}, ['millennium']={'millennium','p'}, ['million']={'million','p'}, ['billion']={'billion','p'}, -- calendar ['julian']={'julian','c'}, ['islamic']={'islamic','c'}, ['hijri']={'islamic','c'}, -- "Turn of the ..." options are a combos which will be handled in similar way as calendars ['turn of the century']={'turn of the century','c'}, ['century turn']={'turn of the century','c'}, ['turn of the decade']={'turn of the decade','c'}, ['decade turn']={'turn of the decade','c'}, ['turn of the year']={'turn of the year','c'}, ['year turn']={'turn of the year','c'}, -- other dates which will be handled in similar way as calendars ['?']={'unknown','c'}, ['unknown']={'unknown','c'}, ['unknown year']={'year unknown','c'}, ['year unknown']={'year unknown','c'}, } return p dewwfi4vqnuvm2x2yyc13jj2an5fuuv 803611 803610 2020-11-24T21:55:02Z en>Pppery 0 Use c. instead of circa per [[Template talk:Wikidata date]] 803611 Scribunto text/plain --[[ __ __ _ _ ___ _ ___ __ _ _ _ | \/ | ___ __| |_ _| | ___ _|_ _/ |( _ ) _ __ / /__ ___ _ __ ___ _ __ | | _____ __ __| | __ _| |_ ___ | |\/| |/ _ \ / _` | | | | |/ _ (_)| || |/ _ \| '_ \ / / __/ _ \| '_ ` _ \| '_ \| |/ _ \ \/ / / _` |/ _` | __/ _ \ | | | | (_) | (_| | |_| | | __/_ | || | (_) | | | |/ / (_| (_) | | | | | | |_) | | __/> < | (_| | (_| | || __/ |_| |_|\___/ \__,_|\__,_|_|\___(_)___|_|\___/|_| |_/_/ \___\___/|_| |_| |_| .__/|_|\___/_/\_\ \__,_|\__,_|\__\___| |_| This code was copied from Commons please request changes there. Maintainers: *Jarekt ]] local function Ordinal(...) return require('Module:Ordinal-cd')._Ordinal(...) end local function Roman(...) return require('Module:Roman-cd')._Numeral(...) end -- ========================================================================== -- === Local Helper functions ============================================== -- ========================================================================== -- form of French "of" based on "precision", which is wikibase compatible date precision format: -- 6=millennium, 7=century, 8=decade, 9=year, 10=month, 11=day -- 1 2 3 4 5 mill cent deca year month day local frOf = {'du','du','du','du','du', 'du', 'du', 'de', 'de', 'de', 'du'} local function decYear(state,lang,case) --Declension of word "year" in several languages if state.precision == 9 then yearCase = { fi={xxx='vuosien', gen='vuoden', ill='vuoteen', ptv='vuotta', ela='vuodesta', ine='vuonna'}, sl={insP='letoma', ins='letom', gen='leta', loc='letu'}, hsb={insP='lětami', ins='lětom', gen='lěta', loc='lěće'} } return yearCase[lang][case] .. ' ' else return '' end end local function decCentury(lang,case) --Declension of word "century" in several languages centuryCase = { pl={nom='wiek', gen='wieku', ins='wiekiem', loc='wieku', default = 'wiek'}, hsb={nom='lětstotk', gen='lětstotka', ins='lětstotkom', loc='lětstotku', default = 'lětstotk'} } return centuryCase[lang][case] or centuryCase[lang]['default'] end local function decMillennium(lang,case) --Declension of word "century" in several languages millenniumCase = { pl={nom='tysiąclecie', gen='tysiąclecia', ins='tysiącleciem', loc='tysiącleciu', default = 'tysiąclecie'}, hsb={nom='lěttysac', gen='lěttysaca', ins='lěttysacom', loc='lěttysacu', default = 'lěttysac'} } return millenniumCase[lang][case] or millenniumCase[lang]['default'] end -- ========================================================================== -- === Output data structure =============================================== -- ========================================================================== local p = {} --[[ Multiple formats are possible: 1) string with key words "$date1" and "$date2" which will be replaced later by the date strings 2) function which can be used to create output string * Function INPUTS: - date1 and date2 string with date strings. "$date1" and "$date2" can also be used which will be replaced latter - state is a table with full description of the phrase we are trying to construct. It includes state.precision1 (and 2) which is wikibase compatible date precision format: 6=millennium, 7=century, 8=decade, 9=year, 10=month, 11=day 3) table with following fields: * table[1] is either string or a function * table.case is a 2 element table with Grammatical cases used by given language to alter month name or other adjective related to date # 1 and 2 ]] p.Translations = { -- ========================================== -- === Two date phrases ===================== -- ========================================== ['from-until'] = { nDates = 2, ar='من $date1 إلى $date2', ['be-tarask']={'з $date1 да $date2', case={'gen','gen'}}, bn='$date1 থেকে $date2 পর্যন্ত', ca='$date1 fins $date2', ckb='لە $date1 تاوەکوو $date2', cs='$date1 až $date2', da='fra $date1 til $date2', de='$date1 bis $date2', el='από $date1 έως $date2', en='from $date1 until $date2', ['en-gb']='from $date1 until $date2', ['en-ca']='from $date1 until $date2', eo='de $date1 al $date2', es='de $date1 a $date2', et='$date1 kuni $date2', fa='از $date1 تا $date2', fi='$date1–$date2', -- välisenä aikana fr=function(date1, date2, state) -- could actually be better should be "d'octobre à mars 2013' not "de octore 2013 à mars 2013" if state.precision > 10 then -- precision > month return 'du $date1 au $date2' else -- "DE septebmbre" / "D'octobre" return require("Module:Linguistic").of(date1, 'fr') .. " à $date2" end end, gl='de $date1 a $date2', --Template:From until used 'desde $date1 ata $date2' he='מ- $date1 עד $date2', hr='od $date1 do $date2', hsb={'wot $date1 do $date2', case={'gen','gen'}}, hu='$date1 – $date2', it='dal $date1 al $date2', ja='$date1から$date2まで', mk='од $date1 до $date2', ml='$date1 മുതൽ $date2 വരെ', nb='fra $date1 til $date2', nds='$date1 bet $date2', nn='fra $date1 til $date2', nl='$date1 tot $date2', no='fra $date1 til $date2', pl={'od $date1 do $date2', case={'gen','gen'}}, ro='din $date1 până în $date2', pt='de $date1 a $date2', qu="$date1 p'unchawmanta $date2 p\'unchawkama", ru={'с $date1 по $date2', case={'gen','gen'}}, sk={'od $date1 do $date2', case={'gen','gen'}}, sl={'od $date1 do $date2', case={'gen','gen'}}, sv='från $date1 till $date2', uk={'з $date1 до $date2', case={'gen','gen'}}, ur='از $date1 تا $date2', vi='từ $date1 đến $date2', ['zh-hans']='从$date1到$date2', ['zh-hant']='從$date1到$date2', default='<span style="color:red">$date1 - $date2</span>', }, ['between']={ nDates = 2, ar='بين $date1 و $date2', ['be-tarask']={'між $date1 і $date2', case={'ins','ins'}}, bn='$date1 এবং $date2-এর মাঝামাঝি', ca='entre $date1 i $date2', ckb='لەنێوان $date1 و $date2', cs={'mezi $date1 a $date2', case={'ins','ins'}}, da='mellem $date1 og $date2', de='zwischen $date1 und $date2', el='μεταξύ $date1 και $date2', en='between $date1 and $date2', ['en-gb']='between $date1 and $date2', ['en-ca']='between $date1 and $date2', eo='inter $date1 kaj $date2', es='entre $date1 y $date2', et='$date1 ja $date2 vahel', fa='بین $date1 و $date2', fi={function(date1, date2, state) return decYear(state,'fi','xxx') .. '$date1 ja $date2 välisenä aikana' end, case={'gen','gen'}}, fr='entre $date1 et $date2', gl='entre $date1 e $date2', he='בין $date1 ו- $date2', hr='između $date1 i $date2', hsb={'mjez $date1 a $date2', case={'ins','ins'}}, hu='$date1 és $date2 között', hi='$date1 और $date2 के बीच', it='tra il $date1 e il $date2', ja='$date1と$date2の間', mk='помеѓу $date1 и $date2', ml='$date1, $date2 എന്നിവയ്ക്കിടയ്ക്ക്', nb='mellom $date1 og $date2', nds='twischen $date1 un $date2', nn='mellom $date1 og $date2', nl='tussen $date1 en $date2', no='mellom $date1 og $date2', pl={'między $date1 a $date2', case={'ins','ins'}}, ro='între $date1 și $date2', pt='entre $date1 e $date2', qu="$date1 p'unchawpa $date2 p'unchawpapas chawpin", ru={'между $date1 и $date2', case={'ins','ins'}}, sk={'medzi $date1 a $date2', case={'ins','ins'}}, sl={function(date1, date2, state) return 'med ' .. decYear(state,'sl','insP') .. '$date1 in $date2' end, case={'ins','ins'}}, sv='mellan $date1 och $date2', uk={'між $date1 та $date2', case={'ins','ins'}}, ur='بین $date1 و $date2', vec='tra el $date1 e el $date2', vi='giữa $date1 và $date2', ['zh-hans']='在$date1到$date2之间', ['zh-hant']='在$date1到$date2之間', default='<span style="color:red">$date1 - $date2</span>', }, ['or'] = { nDates = 2, ar='$date1 أو $date2', ['be-tarask']='$date1 ці $date2', bn='$date1 অথবা $date2', ca='$date1 o $date2', ckb='$date1 یان $date2', cs='$date1 nebo $date2', da='$date1 eller $date2', de='$date1 oder $date2', el='$date1 ή $date2', en='$date1 or $date2', ['en-gb']='$date1 or $date2', ['en-ca']='$date1 or $date2', eo='$date1 aŭ $date2', es='$date1 o $date2', et='$date1 või $date2', fa='$date1 یا $date2', fi='$date1 tai $date2', fr='$date1 ou $date2', gl='$date1 ou $date2', he='$date1 או $date2', hr='$date1 ili $date2', hsb='$date1 abo $date2', hu='$date1 vagy $date2', it='$date1 o $date2', ja='$date1または$date2', mk='$date1 или $date2', ml='$date1 അഥവാ $date2', nb='$date1 eller $date2', nn='$date1 eller $date2', nds='$date1 oder $date2', nl='$date1 of $date2', no='$date1 eller $date2', pl='$date1 lub $date2', pt='$date1 ou $date2', qu='$date1 icha $date2', ro='$date1 sau $date2', ru='$date1 или $date2', sk='$date1 alebo $date2', sl='$date1 ali $date2', sv='$date1 eller $date2', uk='$date1 або $date2', ur='$date1 یا $date2', th='$date1 หรือ $date2', vi='$date1 hoặc $date2', ['zh-hans']='$date1或$date2', ['zh-hant']='$date1或$date2', default=function(date1, date2, state) return require("Module:linguistic").conj({date1, date2}, state.lang, 'or') end, }, ['and'] = { nDates = 2, ar='$date1 و $date2', ['be-tarask']='$date1 і $date2', bn='$date1 এবং $date2', ca='$date1 i $date2', ckb='$date1 و $date2', cs='$date1 a $date2', da='$date1 og $date2', de='$date1 und $date2', el='$date1 και $date2', en='$date1 and $date2', ['en-gb']='$date1 and $date2', ['en-ca']='$date1 and $date2', eo='$date1 kaj $date2', es='$date1 y $date2', et='$date1 ja $date2', fa='$date1 و $date2', fi='$date1 ja $date2', fr='$date1 et $date2', gl='$date1 e $date2', he='$date1 ו- $date2', hu='$date1 és $date2', hsb='$date1 a $date2', it='$date1 e $date2', ja='$date1および$date2', mk='$date1 и $date2', ml='$date1 ഒപ്പം $date2', nb='$date1 og $date2', nn='$date1 og $date2', nds='$date1 un $date2', nl='$date1 en $date2', no='$date1 og $date2', pl='$date1 i $date2', pt='$date1 e $date2', qu='$date1wan $date2pas', ro='$date1 și $date2', ru='$date1 и $date2', sk='$date1 a $date2', sl='$date1 in $date2', sv='$date1 och $date2', uk='$date1 та $date2', ur='$date1 و $date2', vi='$date1 và $date2', ['zh-hans']='$date1和$date2', ['zh-hant']='$date1和$date2', default=function(date1, date2, state) return require("Module:linguistic").conj({date1, date2}, state.lang, 'and') end, }, -- ========================================== -- === Prepositions ========================= -- ========================================== ['by'] = { nDates = 1, ar='حوالي $date1', ['be-tarask']={'да $date1', case={'gen',''}}, bn='$date1 কর্তৃক', ca='vers $date1', ckb='نزیکی $date1', cs={'kolem $date1', case={'gen',''}}, da='mod $date1', de='spätestens $date1', el='έως $date1', en='by $date1', ['en-gb']='by $date1', ['en-ca']='by $date1', eo='ne post $date1', es='para $date1', et='hiljemalt $date1', fa='توسط $date1', fi={function(date1, state) return decYear(state,'fi','ill') .. '$date1 mennessä' end, case={'ill',''}}, fr='$date1 (au plus tard)', gl='cara a $date1', he='עד $date1', hr='do $date1', hsb={'do $date1', case={'gen',''}}, it='entro il $date1', ja='$date1まで', mk='кон $date1', ml='$date1-ൽ', nb='innen $date1', nn='innan $date1', nds='$date1 oder vördem al', nl='tegen $date1', no='innen $date1', pl={'przed $date1', case={'ins',''}}, pt='por $date1', qu="$date1 p'unchawkama", ro='de $date1', ru={'к $date1', case={'dat',''}}, sk={'okolo $date1', case={'gen',''}}, sl={function(date1, state) return 'najpozneje ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='senast $date1', uk={'до $date1', case={'dat',''}}, ur='توسط $date1', vi='vào $date1', -- need to insert "đã" somewhere later in the sentence --> ['zh-hans']='$date1前后', ['zh-hant']='$date1前后', default='<span style="color:red">by $date1</span>', }, ['before'] = { nDates = 1, ar='قبل $date1', ['be-tarask']={'перад $date1', case={'ins',''}}, bn='$date1-এর আগে', bg='Преди $date1', ca='abans $date1', ckb='پێش $date1', cs={'před $date1', case={'ins',''}}, da='før $date1', de='vor $date1', el='πριν από $date1', en='before $date1', ['en-gb']='before $date1', ['en-ca']='before $date1', eo='antaŭ $date1', es='antes de $date1', et='enne $date1', fa='پیش از $date1', fi={function(date1, state) return 'ennen ' .. decYear(state,'fi','ptv') .. '$date1' end, case={'ptv',''}}, fr='avant $date1', gl='antes do $date1', he='לפני $date1', hr='prije $date1', hsb={'do $date1', case={'gen',''}}, hu='$date1 előtt', it='prima del $date1', ja='$date1より前', mk='пред $date1', ml='$date1-നു മുമ്പ്', nb='før $date1', nn='før $date1', nds='vör $date1', nl='voor $date1', no='før $date1', pl={'przed $date1', case={'ins',''}}, pt='antes de $date1', qu="$date1 p'unchaw ñawpan", ro='înainte de $date1', ru={'ранее $date1', case={'gen',''}}, sk={'pred $date1', case={'ins',''}}, sl={function(date1, state) return 'pred ' .. decYear(state,'sl','ins') .. ' $date1' end, case={'ins',''}}, sv='före $date1', uk={'до $date1', case={'gen',''}}, ur='قبل $date1', vi='trước $date1', ['zh-hans']='在$date1之前', ['zh-hant']='在$date1之前', default='<span style="color:red">before $date1</span>', }, ['from'] = { nDates = 1, ar='من $date1', ['be-tarask']={'з $date1', case={'gen',''}}, bn='$date1 থেকে', ckb='لە $date1', cs={'od $date1', case={'gen',''}}, de='Seit $date1', el='από $date1', en='from $date1', ['en-gb']='from $date1', ['en-ca']='from $date1', eo='ekde $date1', es='desde $date1', et='alates $date1', fa='از $date1', fi={function(date1, state) return decYear(state,'fi','ela') .. '$date1 alkaen' end, case={'ela',''}}, fr= function(date1, state) return 'à partir '.. frOf[state.precision] ..' $date1' end, gl='desde o $date1', he='מ- $date1', hr='od $date1', hsb={'wot $date1', case={'gen',''}}, it='dal $date1', ja='$date1から', nds='vonaf $date1', mk='од $date1', nb='fra $date1', nn='frå $date1', ml='$date1 മുതൽ', nl='vanaf $date1', no='fra $date1', pl={'od $date1', case={'gen',''}}, qu="$date1 p'unchawmanta", ro='din $date1', ru={'С $date1', case={'gen',''}}, sk={'od $date1', case={'gen',''}}, sl={function(date1, state) return 'od ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='från $date1', uk={'з $date1', case={'gen',''}}, ur='از $date1', vi='từ $date1', ['zh-hans']='自$date1', ['zh-hant']='自$date1', default='<span style="color:red">from $date1</span>', }, ['until'] = { nDates = 1, ['be-tarask']={'усутыч да $date1', case={'gen',''}}, bn='$date1 পর্যন্ত', ckb='تاوەکوو $date1', cs={'do $date1', case={'gen', ''}}, de='bis $date1', en='until $date1', ['en-gb']='until $date1', ['en-ca']='until $date1', eo='ĝis $date1', es='hasta $date1', fr=function(date1, state) if state.precision > 10 then -- precision > month return "jusqu'au $date1" else return "jusqu'à $date1" end end, hr='do $date1', hsb={'do $date1', case={'gen',''}}, it='al $date1', nb='til $date1', nl='tot $date1', nn='til $date1', ml='$date1 വരെ', pl={'do $date1', case={'gen',''}}, ru={'вплоть до $date1', case={'gen',''}}, sk={'do $date1', case={'gen', ''}}, uk={'аж до $date1', case={'gen',''}}, ur='تک $date1', ['zh-hans']='直到$date1', ['zh-hant']='直到$date1', default='<span style="color:red">until $date1</span>', }, ['after'] = { nDates = 1, ar='بعد $date1', ['be-tarask']={'пасьля $date1', case={'gen',''}}, bn='$date1-এর পরে', ca='després de $date1', ckb='دوای $date1', cs={'po $date1', case={'loc',''}}, de='nach $date1', da='efter $date1', el='μετά από $date1', en='after $date1', ['en-gb']='after $date1', ['en-ca']='after $date1', eo='post $date1', es='después de $date1', et='pärast $date1', fa='پس از $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 jälkeen' end, case={'gen',''}}, fr='après $date1', gl='despois do $date1', he='לאחר $date1', hr='nakon $date1', hu='$date1 után', hsb={'po $date1', case={'loc',''}}, it='dopo il $date1', ja='$date1より後', mk='по $date1', ml='$date1-നു ശേഷം', nb='etter $date1', nn='etter $date1', nds='na $date1', nl='na $date1', no='etter $date1', pl={'po $date1', case={'loc',''}}, pt='após $date1', qu="$date1 p'unchaw qhipan", ro='după $date1', ru={'после $date1', case={'gen',''}}, sk={'po $date1', case={'loc',''}}, sl={function(date1, state) return 'po ' .. decYear(state,'sl','loc') .. ' $date1' end, case={'loc',''}}, sv='efter $date1', uk={'після $date1', case={'gen',''}}, ur='بعد $date1', vi='sau $date1', ['zh-hans']='在$date1之后', ['zh-hant']='在$date1之後', default='<span style="color:red">after $date1</span>', }, -- ========================================== -- === Adjectives =========================== -- ========================================== ['early'] = { nDates = 1, ar='بداية $date1', ['be-tarask']={'пачатак $date1', case={'gen',''}}, bn='$date1-এর শুরুতে', ca='al principi de $date1', ckb='سەرەتای $date1', cs={'začátek $date1', case={'gen',''}}, da='tidlig $date1', de='Anfang $date1', el='αρχές του $date1', en='early $date1', ['en-gb']='early $date1', ['en-ca']='early $date1', eo='komence de $date1', es='a principios de $date1', et='$date1 algus', fa='اوایل $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 alku' end, case={'gen',''}}, fr='début $date1', gl='a principios de $date1', he='תחילת $date1', hr='početak $date1', hsb={'spočatk $date1', case={'gen',''}}, hu='$date1 elején', it='inizio del $date1', ja='$date1初頭', mk='почеток на $date1', ml='$date1 ആദ്യപാദം', nb='tidlig $date1', nn='tidleg $date1', nds='Anfang $date1', nl='begin $date1', no='tidlig $date1', pl={'początek $date1', case={'gen',''}}, pt='a princípios de $date1', qu="$date1 ñawpaq p'unchawkunapi", ro='începând cu $date1', ru={'начало $date1', case={'gen',''}}, sk={'začiatok $date1', case={'gen',''}}, sl=function(date1, state)-- could actually be better should be "d'octobre à mars 2013' not "de octore 2013 à mars 2013" if state.precision == 9 then -- precision > month return 'zgodnje leto $date1' else -- "DE septebmbre" / "D'octobre" return 'zgodnji $date1' end end, sv='början av $date1', uk={'початок $date1', case={'gen',''}}, ur='ابتدا $date1', vi='đầu năm $date1', ['zh-hans']='$date1早期', ['zh-hant']='$date1早期', default='<span style="color:red">early $date1</span>', }, ['mid'] = { nDates = 1, ar='منتصف $date1', ['be-tarask']={'сярэдзіна $date1', case={'gen',''}}, bn='$date1-এর মাঝামাঝি', ca='a mitjan $date1', ckb='ناوەڕاستی $date1', cs={'polovina $date1', case={'gen',''}}, da='midten af $date1', de='Mitte $date1', el='μέσα του $date1', en='mid $date1', ['en-gb']='mid $date1', ['en-ca']='mid $date1', eo='meze de $date1', es='a mediados de $date1', et='$date1 keskel', fa='اواسط $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 puoliväli' end, case={'gen',''}}, fr='mi $date1', gl='a mediados de $date1', he='אמצע $date1', hr='sredina $date1', hsb={'srjedź $date1', case={'gen',''}}, hu='$date1 közepén', it='metà del $date1', ja='$date1半ば', mk='средина на $date1', ml='$date1 മദ്ധ്യം', nb='midten av $date1', nn='midten av $date1', nds='Midd $date1', nl='midden $date1', no='midten av $date1', pl={'środek $date1', case={'gen',''}}, pt='meados de $date1', qu="$date1 chawpin p'unchawkunapi", ro='mijlocul lui $date1', ru={'середина $date1', case={'gen',''}}, sk={'polovica $date1', case={'gen',''}}, sl={function(date1, state) return 'sredina ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='mitten av $date1', uk={'середина $date1', case={'gen',''}}, ur='وسط $date1', vi='giữa năm $date1', ['zh-hans']='$date1中期', ['zh-hant']='$date1中期', default='<span style="color:red">mid $date1</span>', }, ['late'] = { nDates = 1, ar='نهاية $date1', bn='$date1-এর শেষে', ['be-tarask']={'канец $date1', case={'gen',''}}, ca='al final de $date1', ckb='کۆتاییی $date1', cs='konec $date1', da='sen $date1', de='Ende $date1', el='τέλη του $date1', en='late $date1', ['en-gb']='late $date1', ['en-ca']='late $date1', eo='fine de $date1', es='a fines de $date1', et='$date1 lõpp', fa='اواخر $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 loppu' end, case={'gen',''}}, fr='fin $date1', gl='a finais de $date1', he='סוף $date1', hr='kraj $date1', hsb={'kónc $date1', case={'gen',''}}, hu='$date1 végén', it='fine del $date1', ja='$date1末', mk='крајот на $date1', ml='$date1 അന്ത്യപാദം', nb='sent $date1', no='seint $date1', nds='Enn $date1', nl='eind $date1', no='sent $date1', pl={'koniec $date1', case={'gen',''}}, pt='ao final de $date1', qu="$date1 qhipaq p'unchawkunapi", ro='sfârșitul lui $date1', ru={'конец $date1', case={'gen',''}}, sk={'koniec $date1', case={'gen',''}}, sl={function(date1, state) return 'pozno ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='slutet av $date1', uk={'кінець $date1', case={'gen',''}}, ur='اخیر $date1', vi='cuối năm $date1', ['zh-hans']='$date1晚期', ['zh-hant']='$date1晚期', default='<span style="color:red">late $date1</span>', }, ['circa'] = { nDates = 1, ar='حوالي $date1', ['be-tarask']={'каля $date1', case={'gen',''}}, bn='প্রায় $date1', ckb='بەنزیکی $date1', ca='cap a $date1', cs='cca $date1', da='ca. $date1', de='etwa $date1', el='περ. $date1', en='c. $date1', ['en-gb']='circa $date1', ['en-ca']='circa $date1', eo='ĉirkaŭ $date1', es='hacia $date1', -- suggestion use "hacia"/"hacia el" or leave the latin circa or ca. et='ca. $date1', fa='حوالی $date1', -- template:Circa used 'حدود $date1' fi={function(date1, state) return 'suunnilleen ' .. decYear(state,'fi','ine') .. '$date1' end, case={'ine',''}}, -- template:Circa used 'noin $date1' fr=function(date1, state) if state.precision >= 11 then -- precision >= day return 'vers le $date1' else return 'vers $date1' end end, gl='entre $date1', -- template:Circa used 'cara a $date1' he='בסביבות $date1', hr='oko $date1', hsb={'wokoło $date1', case={'gen',''}}, hu='$date1 körül', it='$date1 circa', ja='$date1頃', ko='$date1경', mk='~ $date1', -- template:Circa used 'o. $date1' ml= 'ഉദ്ദേശം $date1', nb='ca. $date1', nn='ca. $date1', nds='üm un bi $date1', nl='ca. $date1', no='ca. $date1', pl={'około $date1', case={'gen',''}}, pt='cerca de $date1', qu='$date1chá', ro='circa $date1', ru={'около $date1', case={'gen',''}}, sk='cca $date1', sl={function(date1, state) return 'ok. ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='c:a $date1', -- template:Circa used 'ca $date1' uk={'близько $date1', case={'gen', ''}}, ur='قریباً $date1', vi='vào khoảng $date1', ['zh-hans']='约$date1', ['zh-hant']='約$date1', default='<span style="color:red">circa $date1</span>', }, ['uncertain'] = { nDates = 1, ['zh-hans']='$date1(?)', ['zh-hant']='$date1(?)', default='$date1 (?)', }, probably = { -- see Q56644435 nDates = 1, ast = 'probablemente $date1', bn = 'সম্ভবত $date1', ca = 'probablement $date1', ckb = 'لەوانەیە $date1', cs = 'pravděpodobně $date1', de = 'wahrscheinlich $date1', el = 'πιθανόν $date1', en = 'probably $date1', ['en-gb']='probably $date1', ['en-ca']='probably $date1', eo = 'probable $date1', es = 'probable $date1', fa = 'احتمالاً $date1', fr = 'probablement $date1', he = 'כפי הנראה $date1', hu = 'feltehetőleg $date1', it = 'probabile $date1', mk = 'веројатно $date1', ml = 'ഒരുപക്ഷേ $date1', nl = 'waarschijnlijk $date1', pl = 'prawdopodobnie $date1', pt = 'provavelmente $date1', ru = 'вероятно $date1', sv = 'antagligen $date1', uk = 'ймовірно $date1', default='<span style="color:red">probably $date1</span>', }, possibly = { -- see Q30230067 nDates = 1, ast = 'posiblemente $date1', bn = 'যথাসম্ভব $date1', ckb = 'لەوانەیە $date1', cs = 'možná $date1', de = 'möglicherweise $date1', en = 'possibly $date1', ['en-gb']='possibly $date1', ['en-ca']='possibly $date1', eo = 'eble $date1', el = 'πιθανόν $date1', fa = 'احتمالاً $date1', fr = 'peut-être $date1', hu = 'talán $date1', mk = 'можеби $date1', ml = 'മിക്കവാറും $date1', nl = 'mogelijk $date1', pl = 'być może $date1', pt = 'possivelmente $date1', ro = 'poate $date1', ru = 'возможно $date1', sv = 'möjligen $date1', uk = 'можливо $date1', default='<span style="color:red">possibly $date1</span>', }, presumably = { -- see Q18122778 nDates = 1, ast = 'presumiblemente $date1', ckb = 'دەشێت $date1', de = 'vermutlich $date1', en = 'presumably $date1', ['en-gb']='presumably $date1', ['en-ca']='presumably $date1', fr = 'présumé $date1', it = 'presumibilmente $date1', ml = 'ഊഹപ്രകാരം $date1', ru = 'предположительно $date1', pl = 'przypuszczalnie $date1', uk = 'здогадно $date1', default='<span style="color:red">presumably $date1</span>', }, ['year unknown']= { nDates = 0, ca='Any desconegut', ['be-tarask']='Год невядомы', bn='অজানা বছর', ckb='ساڵی نەزاندراو', cs='neznámý rok', de='Jahr unbekannt', el='άγνωστο έτος', en='year unknown', ['en-gb']='year unknown', ['en-ca']='year unknown', eo='jaro nesciata', es='Año desconocido', fa='سال نامعلوم', fi='tuntematon vuosi', fr='Année inconnue', he='שנה לא ידועה', hsb='lěto njeznate', hr='nepoznata godina', it='anno sconosciuto', ja='年代不明', la='Sine anno', ml='വർഷം ലഭ്യമല്ല', mk='непозната година', nb='ukjent år', nn='ukjent år', nl='zonder jaar', no='ukjent år', pl='rok nieznany', qu='Mana riqsisqa wata', ru='Год неизвестен', sk='neznámy rok', sl='neznano leto', sv='Okänt år', uk='рік невідомий', ur='سال نامعلوم', vi='không rõ năm', ['zh-hans']='年份未知', ['zh-hant']='年份未知', default='<span style="color:red">year unknown</span>', }, -- ========================================== -- === Parts of a whole ===================== -- ========================================== ['firsthalf'] = { nDates = 1, ar='النصف الأول من $date1', ['be-tarask']={'першая палова $date1', case={'gen',''}}, bn='$date1-এর প্রথমার্ধ', ca='1a meitat de $date1', ckb='نیوەی یەکەمی $date1', cs={'1. polovina $date1', case={'gen',''}}, da='1. halvdel af $date1', de='1. Hälfte $date1', el='πρώτο μισό του $date1', en='first half of $date1', ['en-gb']='first half of $date1', ['en-ca']='first half of $date1', eo='unua duono de $date1', es='primera mitad de $date1', et='$date1 esimene pool', fa='نیمهٔ نخست $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 alkupuolisko' end, case={'gen',''}}, fr='1<sup>re</sup> moitié de $date1', gl='primeira metade de $date1', he='במחצית הראשונה של $date1', hsb={'prěnja połojca $date1', case={'gen',''}}, hu='$date1 első felében', hr='prva polovina $date1', it='prima metà del $date1', ja='$date1上半期', mk='прва половина на $date1', ml='$date1 ആദ്യപകുതി', nb='første halvdel av $date1', nn='fyrste halvdelen av $date1', nds='1. Helft $date1', nl='eerste helft $date1', no='første halvdel av $date1', pl={'pierwsza połowa $date1', case={'gen',''}}, pt='primeira metade de $date1', qu='$date1 ñawpaq kuskanpi', ro='prima jumătate a lui $date1', ru={'первая половина $date1', case={'gen',''}}, sk={'1. polovica $date1', case={'gen',''}}, sl={function(date1, state) return 'prva polovica ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='första halvan av $date1', uk={'перша половина $date1', case={'gen',''}}, ur='نصف اول $date1', vi='nửa đầu năm $date1', ['zh-hans']='$date1上半期', ['zh-hant']='$date1上半期', default='<span style="color:red">first half of $date1</span>', }, ['secondhalf'] = { nDates = 1, ar='النصف الثاني من $date1', ['be-tarask']={'другая палова $date1', case={'gen',''}}, bn='$date1-এর দ্বিতীয়ার্ধ', ca='2a meitat de $date1', ckb='نیوەی دووەمی $date1', cs={'2. polovina $date1', case={'gen',''}}, da='2. halvdel af $date1', de='2. Hälfte $date1', el='δεύτερο μισό του $date1', en='second half of $date1', ['en-gb']='second half of $date1', ['en-ca']='second half of $date1', eo='dua duono de $date1', es='segunda mitad de $date1', et='$date1 teine pool', fa='نیمهٔ دوم $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 loppupuolisko' end, case={'gen',''}}, fr='2<sup>e</sup> moitié de $date1', gl='segunda metade de $date1', he='במחצית השנייה של $date1', hsb={'druha połojca $date1', case={'gen',''}}, hr='druga polovina $date1', hu='$date1 második felében', it='seconda metà del $date1', ja='$date1下半期', mk='втора половина на $date1', ml='$date1 രണ്ടാം പകുതി', nb='andre halvdel av $date1', nn='andre halvdelen av $date1', nds='2. Helft vun $date1', nl='tweede helft $date1', no='andre halvdel av $date1', pl={'druga połowa $date1', case={'gen',''}}, pt='segunda metade de $date1', qu='$date1 qhipaq kuskanpi', ro='a doua jumătate a lui $date1', ru={'вторая половина $date1', case={'gen',''}}, sk={'2. polovica $date1', case={'gen',''}}, sl={'druga polovica {{IfNum | $date0 | leta}} $date1', case={'gen',''}}, sv='andra halvan av $date1', uk={'друга половина $date1', case={'gen',''}}, ur='نصف آخر $date1', vi='nửa cuối năm $date1', ['zh-hans']='$date1下半期', ['zh-hant']='$date1下半期', default='<span style="color:red">second half of $date1</span>', }, ['1quarter']= { nDates = 1, bn='$date1-এর প্রথম ত্রৈমাসিক', ['be-tarask']={'першая чвэрць $date1', case={'gen',''}}, ckb='چارەکی یەکەمی $date1', cs={'1. čtvrtina $date1', case={'gen',''}}, de='erstes Viertel $date1', en='first quarter of $date1', ['en-gb']='first quarter of $date1', ['en-ca']='first quarter of $date1', eo='unua kvarono de $date1', es='primer cuarto del $date1', fa='ربع نخست $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 ensimmäinen neljännes' end, case={'gen',''}}, fr= function(date1, state) return 'premier quart '.. frOf[state.precision] ..' $date1' end, he='רבעון ראשון של $date1', hsb={'prěnja štwórćina $date1', case={'gen',''}}, hr='prva četvrtina $date1', it='primo quarto del $date1', ja='$date1第1四半期', ml='$date1 ഒന്നാം ത്രൈമാസം', nb='første kvartal av $date1', nl='eerste kwart $date1', nn='fyrste kvartalet av $date1', no='første kvartal av $date1', pl={'pierwsza ćwiartka $date1', case={'gen',''}}, qu='$date1 ñawpaq ñiqin tawa phatmanpi', ru={'первая четверть $date1', case={'gen',''}}, sk={'1. štvrtina $date1', case={'gen',''}}, sl={'prva četrtina {{IfNum | $date0 | leta}} $date1', case={'gen',''}}, sl={function(date1, state) return 'prva četrtina ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='första kvartalet av $date1', uk={'1-ша чверть $date1', case={'gen',''}}, ur='پہلی سہ ماہی $date1', vi='quý 1 năm $date1', ['zh-hans']='$date1第一季度', ['zh-hant']='$date1第一季度', default='<span style="color:red">first quarter of $date1</span>', }, ['2quarter']= { nDates = 1, ['be-tarask']={'другая чвэрць $date1', case={'gen',''}}, bn='$date1-এর দ্বিতীয় ত্রৈমাসিক', ckb='چارەکی دووەمی $date1', cs={'2. čtvrtina $date1', case={'gen',''}}, de='zweites Viertel $date1', en='second quarter of $date1', ['en-gb']='second quarter of $date1', ['en-ca']='second quarter of $date1', eo='dua kvarono de $date1', es='segundo cuarto del $date1', fa='ربع دوم $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 toinen neljännes' end, case={'gen',''}}, fr= function(date1, state) return 'deuxième quart '.. frOf[state.precision] ..' $date1' end, he='רבעון שני של $date1', hsb={'druha štwórćina $date1', case={'gen',''}}, hr='druga četvrtina $date1', it='secondo quarto del $date1', ja='$date1第2四半期', ml='$date1 രണ്ടാം ത്രൈമാസം', nb='andre kvartal av $date1', nl='tweede kwart $date1', nn='andre kvartalet av $date1', no='andre kvartal av $date1', pl={'druga ćwiartka $date1', case={'gen',''}}, qu='$date1 iskay ñiqin tawa phatmanpi', ru={'вторая четверть $date1', case={'gen',''}}, sk={'2. štvrtina $date1', case={'gen',''}}, sl={function(date1, state) return 'druga četrtina ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='andra kvartalet av $date1', uk={'2-га чверть $date1', case={'gen',''}}, ur='دوسری سہ ماہی $date1', vi='quý 2 năm $date1', ['zh-hans']='$date1第二季度', ['zh-hant']='$date1第二季度', default='<span style="color:red">second quarter of $date1</span>', }, ['3quarter']= { nDates = 1, ['be-tarask']={'трэцяя чвэрць $date1', case={'gen',''}}, bn='$date1-এর তৃতীয় ত্রৈমাসিক', ckb='چارەکی سێیەمی $date1', cs={'3. čtvrtina $date1', case={'gen',''}}, de='drittes Viertel $date1', en='third quarter of $date1', ['en-gb']='third quarter of $date1', ['en-ca']='third quarter of $date1', eo='tria kvarono de $date1', es='tercer cuarto del $date1', fa='ربع سوم $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 kolmas neljännes' end, case={'gen',''}}, fr= function(date1, state) return 'troisième quart '.. frOf[state.precision] ..' $date1' end, he='רבעון שלישי של $date1', hsb={'třeća štwórćina $date1', case={'gen',''}}, hr='treća četvrtina $date1', it='terzo quarto del $date1', ja='$date1第3四半期', ml='$date1 മൂന്നാം ത്രൈമാസം', nb='tredje kvartal av $date1', nl='derde kwart $date1', nn='tredje kvartalet av $date1', no='tredje kvartal av $date1', pl={'trzecia ćwiartka $date1', case={'gen',''}}, qu='$date1 kimsa ñiqin tawa phatmanpi', sk={'3. štvrtina $date1', case={'gen',''}}, sl={function(date1, state) return 'tretja četrtina ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, ru={'третья четверть $date1', case={'gen',''}}, sv='tredje kvartalet av $date1', uk={'3-тя чверть $date1', case={'gen',''}}, ur='تیسری سہ ماہی $date1', vi='quý 3 năm $date1', ['zh-hans']='$date1第三季度', ['zh-hant']='$date1第三季度', default='<span style="color:red">third quarter of $date1</span>', }, ['4quarter']= { nDates = 1, ['be-tarask']={'чацьвёртая чвэрць $date1', case={'gen',''}}, bn='$date1-এর চতুর্থ ত্রৈমাসিক', ckb='چارەکی چوارەمی $date1', cs={'4. čtvrtina $date1', case={'gen',''}}, de='letztes Viertel $date1', en='fourth quarter of $date1', ['en-gb']='fourth quarter of $date1', ['en-ca']='fourth quarter of $date1', eo='kvara kvarono de $date1', es='último cuarto del $date1', fa='ربع چهارم $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 viimeinen neljännes' end, case={'gen',''}}, fr= function(date1, state) return 'quatrième quart '.. frOf[state.precision] ..' $date1' end, he='רבעון רביעי של $date1', hsb={'poslednja štwórćina $date1', case={'gen',''}}, hr='četvrta četvrtina $date1', it='quarto quarto del $date1', ja='$date1第4四半期', ml='$date1 നാലാം ത്രൈമാസം', nb='fjerde kvartal av $date1', nl='vierde kwart $date1', nn='tredje kvartalet av $date1', no='fjerde kvartal av $date1', pl={'czwarta ćwiartka $date1', case={'gen',''}}, qu='$date1 qhipaq ñiqin tawa phatmanpi', ru={'последняя четверть $date1', case={'gen',''}}, sk={'4. štvrtina $date1', case={'gen',''}}, sl={function(date1, state) return 'zadnja četrtina ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='fjärde kvartalet av $date1', uk={'4-та чверть $date1', case={'gen',''}}, ur='چوتھی سہ ماہی $date1', vi='quý 4 năm $date1', ['zh-hans']='$date1第四季度', ['zh-hant']='$date1第四季度', default='<span style="color:red">forth quarter of $date1</span>', }, -- ========================================== -- === Seasons ============================== -- ========================================== ['spring'] = { nDates = 1, ar='ربيع $date1', ['be-tarask']='вясна $date1', bn='বসন্তকাল $date1', ca='primavera $date1', ckb='بەھاری $date1', cs={'jaro $date1', case={'gen', ''}}, da='Forår $date1', de='Frühling $date1', en='Spring $date1', ['en-gb']='Spring $date1', ['en-ca']='Spring $date1', eo='printempo $date1', es='primavera de $date1', et='$date1 kevad', fa='بهار $date1', fi='Kevät $date1', fr='printemps $date1', gl='primavera $date1', he='אביב $date1', hr='proljeće $date1', hsb='nalěćo $date1', hu='$date1 tavasza', it='primavera $date1', ja='{{#if:$date1|$date1年}}春', mk='пролет $date1', ml='$date1 വസന്തം', nb='våren $date1', nds='Vörjohr $date1', nn='våren $date1', nl='voorjaar $date1', no='våren $date1', pl='wiosna $date1', pt='primavera de $date1', ro='primăvara lui $date1', ru='весна $date1', sk={'jar $date1', case={'gen', ''}}, sl='pomlad $date1', sv='våren $date1', uk='весна $date1', ur='بہار $date1', vi='mùa đông năm $date1', ['zh-hans']='{{#if:$date1|$date1年}}春季', ['zh-hant']='{{#if:$date1|$date1年}}春季', default='<span style="color:red">Spring $date1</span>', }, ['summer'] = { nDates = 1, ar='صيف $date1', ['be-tarask']='лета $date1', bn='গ্রীষ্মকাল $date1', ca='estiu $date1', ckb='ھاوینی $date1', cs={'léto $date1', case={'gen', ''}}, da='Sommer $date1', de='Sommer $date1', en='Summer $date1', ['en-gb']='Summer $date1', ['en-ca']='Summer $date1', eo='somero $date1', es='verano de $date1', et='$date1 suvi', fa='تابستان $date1', fi='Kesä $date1', fr='été $date1', gl='verán $date1', he='קיץ $date1', hr='ljeto $date1', hsb='lěćo $date1', hu='$date1 nyara', it='estate $date1', ja='{{#if:$date1|$date1年}}夏', mk='лето $date1', ml='$date1 വേനൽ', nb='sommeren $date1', nds='Sommer $date1', nn='sumaren $date1', nl='zomer $date1', no='sommeren $date1', pl='lato $date1', pt='verão de $date1', ro='vara lui $date1', ru='лето $date1', sk={'leto $date1', case={'gen', ''}}, sl='poletje $date1', sv='sommaren $date1', uk='літо $date1', ur='گرما $date1', vi='mùa hè năm $date1', ['zh-hans']='{{#if:$date1|$date1年}}夏季', ['zh-hant']='{{#if:$date1|$date1年}}夏季', default='<span style="color:red">Summer $date1</span>', }, ['autumn'] = { nDates = 1, ar='خريف $date1', ['be-tarask']='восень $date1', bn='শরত্কাল $date1', ca='tardor $date1', ckb='پاییزی $date1', cs={'podzim $date1', case={'gen', ''}}, da='Efterår $date1', de='Herbst $date1', en='Autumn $date1', ['en-gb']='Autumn $date1', ['en-ca']='Autumn $date1', eo='aŭtuno $date1', es='otoño de $date1', et='$date1 sügis', fa='پاییز $date1', fi='Syksy $date1', fr='automne $date1', gl='outono $date1', he='סתיו $date1', hr='jesen $date1', hsb='nazyma $date1', hu='$date1 ősze', it='autunno $date1', ja='{{#if:$date1|$date1年}}秋', mk='есен $date1', ml='$date1 ശരത്കാലം', nb='høsten $date1', nds='Harvst $date1', nn='hausten $date1', nl='herfst $date1', no='høsten $date1', pl='jesień $date1', pt='outono de $date1', ro='toamna lui $date1', ru='осень $date1', sk={'jeseň $date1', case={'gen', ''}}, sl='jesen $date1', sv='hösten $date1', uk='осінь $date1', ur='خزاں $date1', vi='mùa thu năm $date1', ['zh-hans']='{{#if:$date1|$date1年}}秋季', ['zh-hant']='{{#if:$date1|$date1年}}秋季', default='<span style="color:red">Autumn $date1</span>', }, ['winter'] = { nDates = 1, ar='شتاء $date1', ['be-tarask']='зіма $date1', bn='শীতকাল $date1', ca='hivern $date1', ckb='زستانی $date1', cs={'zima $date1', case={'gen', ''}}, da='Vinter $date1', de='Winter $date1', en='Winter $date1', ['en-gb']='Winter $date1', ['en-ca']='Winter $date1', eo='vintro $date1', es='invierno de $date1', et='$date1 talv', fa='زمستان $date1', fi='Talvi $date1', fr='hiver $date1', gl='inverno $date1', he='חורף $date1', hr='zima $date1', hsb='zyma $date1', hu='$date1 tele', it='inverno $date1', ja='{{#if:$date1|$date1年}}冬', mk='зима $date1', ml='$date1 ശൈത്യകാലം', nds='Winter $date1', nl='winter $date1', nb='vinteren $date1', nn='vinteren $date1', no='vinteren $date1', pl='zima $date1', pt='inverno de $date1', ro='iarna lui $date1', ru='зима $date1', sk={'zima $date1', case={'gen', ''}}, sl='zima $date1', sv='vintern $date1', uk='зима $date1', ur='سرما $date1', vi='mùa đông năm $date1', ['zh-hans']='{{#if:$date1|$date1年}}冬季', ['zh-hant']='{{#if:$date1|$date1年}}冬季', default='<span style="color:red">Winter $date1</span>', }, -- ========================================== -- === Decade/Century/Millennium ============ -- ========================================== ['decade'] = { nDates = 1, ar='أعوام $date1', ['be-tarask']='$date1-я гады', bn='$date1-এর দশক', ca='dècada del $date1', ckb='دەیەی $date1', cs=function(date1, state) return ((date1 % 100 == 0) and 'první dekáda' or ((date1 % 100) .. '. léta')) .. ' ' .. (math.floor(date1 / 100) + 1) .. '. století' end, da="$date1'erne", de='$date1er-Jahre', el='δεκαετία του $date1', en='$date1s', ['en-gb']='$date1s', ['en-ca']='$date1s', eo='$date1-aj jaroj', es='años $date1', et='$date1ndad', fa='دههٔ $date1', fi='$date1-luvulla', fr='années $date1', gl='anos $date1', he='שנות ה-$date1', hr='$date1-ih', hsb='$date1te lěta', hu='$date1-s évek', it="{{#ifexpr: $date1 mod 100 = 0 | primo decennio del {{Roman|{{#expr: ($date1 / 100) + 1}}}} sec. | anni '{{#expr: $date1 mod 100 }} del {{Roman|{{#expr: floor($date1 / 100) + 1}}}} sec.}}", ja='$date1年代', mk='$date1-ти', ml='ദശകം $date1-ൽ', nb='$date1-årene', nn='$date1-åra', nds='Johren $date1', nl='jaren $date1', no='$date1-årene', pl=function(date1, state) local str = 'lata '..(date1 % 100)..'. ' if (date1 % 100 == 0) then str = 'pierwsza dekada ' elseif (date1 % 100 == 10) then str = 'druga dekada ' end return str .. Roman(math.floor(date1 / 100) + 1) .. ' wieku' end, pt='década de $date1', qu='$date1 watakuna', ro='anii $date1', ru='$date1-е годы', sk=function(date1, state) return ((date1 % 100 == 0) and 'prvé desaťročie' or ((date1 % 100) .. '. roky')) .. ' ' .. (math.floor(date1 / 100) + 1) .. '. storočia' end, sl='$date1-ta leta', sv='$date1-talet{{#switch:$date1|1300|1400|1500|1600|1700|1800|1900|2000|2100=&nbsp;(decennium)}}', uk='$date1-ті роки', ur='دہائی $date1', vi='thập niên $date1', ['zh-hans']='$date1年代', ['zh-hant']='$date1年代', default='<span style="color:red">$date1s</span>', }, ['century'] = { nDates = 1, ar='القرن $date1', ['be-tarask']=function(date1, state) return Ordinal(date1, 'be-tarask') .. ' стагодзьдзе' end, bn=function(date1, state) return Ordinal(date1, 'bn') .. ' শতাব্দী' end, ca='segle {{small-caps|{{lc:{{Roman year|$date1}}}}}}', ckb='سەدەی $date1', cs='$date1. století', da='$date1. århundrede', de='$date1. Jahrhundert', el='$date1ος αιώνας', en=function(date1, state) return Ordinal(date1, 'en') .. ' century' end, ['en-gb']=function(date1, state) return Ordinal(date1, 'en-gb') .. ' century' end, ['en-ca']=function(date1, state) return Ordinal(date1, 'en-ca') .. ' century' end, eo='$date1-a jarcento', es=function(date1, state) return 'siglo ' .. Roman(date1) end, et='$date1. sajand', fa='سدهٔ $date1', fi='{{#expr: ($date1 - 1) * 100}}-luvulla', fr=function(date1, state) return Ordinal(date1, 'fr', 'roman', 'm') .. ' siècle' end, gl=function(date1, state) return 'século ' .. Roman(date1) end, he='המאה ה-$date1', hr='$date1. stoljeće', hsb='$date1. lětstotk', hu='$date1. évszázad', it=function(date1, state) return Roman(date1) .. ' sec.' end, ja='$date1世紀', la='saeculum $date1', mk=function(date1, state) return Roman(date1) .. ' век' end, ml='$date1-ആം നൂറ്റാണ്ട്', nb=function(date1, state) return Ordinal(date1, 'nb') .. ' århundre' end, nn=function(date1, state) return 'det ' .. Ordinal(date1, 'no') .. ' århundre' end, nds='$date1. Johrhunnert', nl='$date1e eeuw', no=function(date1, state) return Ordinal(date1, 'no') .. ' århundre' end, pl=function(date1, state) return Roman(date1) .. ' ' .. decCentury('pl',state.case) end, pt=function(date1, state) return 'século ' .. Roman(date1) end, qu='$date1 ñiqin pachakwata', ro='secolul al $date1-lea', ru=function(date1, state) return Roman(date1) .. ' век' end, sk='$date1. storočie', sl=function(date1, state) return Ordinal(date1, 'sl') .. '. stoletje' end, sv='{{#expr:$date1 - 1}}00-talet', tt=function(date1, state) return Roman(date1) .. ' гасыр' end, uk=function(date1, state) return Roman(date1) .. ' ст.' end, ur='صدی $date1', vi='thế kỷ $date1', ['zh-hans']='$date1世纪', ['zh-hant']='$date1世紀', default='<span style="color:red">$date1 century</span>', }, ['millennium'] = { nDates = 1, ['be-tarask']=function(date1, state) return Ordinal(date1, 'be-tarask') .. ' тысячагодзьдзе' end, bn=function(date1, state) return Ordinal(date1, 'bn') .. ' সহস্রাব্দ' end, ckb=function(date1, state) return 'ھەزارەی ' .. Ordinal(date1, 'ckb') end, cs='$date1. tisíciletí', de='$date1. Jahrtausend', en=function(date1, state) return Ordinal(date1, 'en') .. ' millennium' end, ['en-gb']=function(date1, state) return Ordinal(date1, 'en-gb') .. ' millennium' end, ['en-ca']=function(date1, state) return Ordinal(date1, 'en-ca') .. ' millennium' end, eo='$date1-a jarmilo', es=function(date1, state) return Roman(date1) .. ' milenio' end, fa=function(date1, state) return 'هزارهٔ ' .. Ordinal(date1, 'fa') end, fr=function(date1, state) return Ordinal(date1, 'fr', 'roman', 'm') .. ' millénaire' end, hr='$date1. tisućljeće', hsb='$date1. lěttysac', it=function(date1, state) return Roman(date1) .. ' millennio' end, ja='$date1千年紀', la='millennium $date1', ml='$date1-മത്തെ സഹസ്രാബ്ദം', nb=function(date1, state) return Ordinal(date1, 'nb') .. ' årtusen' end, nn=function(date1, state) return Ordinal(date1, 'no') .. ' tusenår' end, nl='$date1e millennium', no=function(date1, state) return Ordinal(date1, 'no') .. ' årtusen' end, pl=function(date1, state) return Roman(date1) .. ' ' .. decMillennium('pl',state.case) end, pt=function(date1, state) return Roman(date1) .. ' milénio' end, qu='$date1 ñiqin waranqawata', ru=function(date1, state) return Roman(date1) .. ' тысячелетие' end, sk='$date1. tisícročie', sl=function(date1, state) return Ordinal(date1, 'sl') .. '. tisočletje' end, sv=function(date1, state) return Ordinal(date1, 'sv') .. ' årtusendet' end, uk=function(date1, state) return Roman(date1) .. ' тис.' end, ur=function(date1, state) return 'ہزارہ ' .. Ordinal(date1, 'ur') end, vi='thiên niên kỷ $date1', ['zh-hans']=function(date1, state) return Ordinal(date1, 'zh-hans') .. '千年' end, ['zh-hant']=function(date1, state) return Ordinal(date1, 'zh-hant') .. '千年' end, --default='{{Please translate|$date1 millennium|Module:I18n/complex date}}', default='<span style="color:red">$date1 millennium</span>', }, ['million'] = { ckb = '$date1 میلیۆن ساڵ', de = '$date1 Millionen Jahre', en = '$date1 million years', ['en-gb'] = '$date1 million years', ['en-ca'] = '$date1 million years', eo = '$date1 milionoj da jaroj', es = '$date1 millones de años', nl = '$date1e miljoen jaar', fr = '$date1 millions d’années', it = '$date1 milioni di anni', ml = function(date1, state) if date1 >= 10 then return (date1 / 10) .. ' കോടി വർഷങ്ങൾ' -- 100 lakh = 1 crore else return (date1 * 10) .. ' ലക്ഷം വർഷങ്ങൾ' end end, nb = '$date1 millioner år', nl = '$date1 miljoen jaar', nn = '$date1 millionar år', pl = '$date1 milionów lat', pt = '$date1 milhões de anos', ru = '$date1 миллиона лет', uk = '$date1 мільйони років', ur = '$date1 ملین سال', ['zh-hans'] = '$date1百万年', ['zh-hant'] = '$date1百万年', default='<span style="color:red">$date1 million years</span>', nDates = 1, }, ['billion'] = { ckb = '$date1 بیلیۆن ساڵ', de = '$date1 Milliarden Jahre', en = '$date1 billion years', ['en-gb'] = '$date1 billion years', ['en-ca'] = '$date1 billion years', es = '$date1 mil millones de años', nl = '$date1e miljard jaar', fr = '$date1 milliard d’années', it = '$date1 miliardi di anni', ml = function(date1, state) return (date1 * 10) .. ' കോടി വർഷങ്ങൾ' end, nb = '$date1 milliarder år', nl = '$date1 miljard jaar', nn = '$date1 milliardar år', pl = '$date1 miliardów lat', pt = '$date1 mil milhões de anos', ru = '$date1 миллиардов лет', uk = '$date1 мільярдів років', ur = '$date1 بلین سال', ['zh-hans'] = '$date1十亿年', ['zh-hant'] = '$date1十億年', default='<span style="color:red$date1 billion years</span>', nDates = 1, }, -- ========================================== -- === Complex phrases ====================== -- ========================================== -- Phrases which for some languages can be build out of simpler phrases -- Their "default" state, used when no translation in the requested language was found, returns a "state" like -- table which is used to call the function again and built the phrase out of simple phrases -- $date1 - original date - 1 -- $date2 - original date -- if era is BC than $date1 and $date2 are swapped ['turn of the century'] = { nDates = 2, ['be-tarask']=function(date1, date2, state) return 'канец ' .. Roman(date1) ..' — пачатак ' .. Roman(date2) ..' стагодзьдзя' end, bn=function(date1, date2, state) return '$date1/' .. Ordinal(date2, 'bn') .. ' শতাব্দীর পালাবদলে' end, ckb=function(date1, date2, state) return 'سەردەمی $date1/' .. Ordinal(date2, 'ckb') .. ' سەدە' end, cs='přelom $date1. a $date2. století', de=function(date1, date2, state) return 'Jahrhundertwende $date1/' .. Ordinal(date2, 'de') end, en=function(date1, date2, state) return 'turn of the $date1/' .. Ordinal(date2, 'en') .. ' century' end, ['en-gb']=function(date1, date2, state) return 'turn of the $date1/' .. Ordinal(date2, 'en-gb') .. ' century' end, ['en-ca']=function(date1, date2, state) return 'turn of the $date1/' .. Ordinal(date2, 'en-ca') .. ' century' end, eo='ŝanĝo de la $date1-a al la $date2-a jarcentoj', es=function(date1, date2, state) return 'hacia finales del siglo ' .. Roman(date1) .. ' y principios del siglo ' .. Roman(date2) .. '' end, fi='{{#expr: $date1 * 100-100}}-luvun loppu ja {{#expr: $date2 * 100-100}}-luvun alku', fr=function(date1, date2, state) return 'fin du ' .. Ordinal(date1, 'fr', 'roman', 'm') .. ' siècle / début du ' .. Ordinal(date2, 'fr', 'roman', 'm') end, hr='kraj $date1. i početak $date2. stoljeća', hsb=function(date1, date2, state) return 'přechod ' .. Roman(date1) ..' do ' .. Roman(date2) ..' lětstotka' end, it=function(date1, date2, state) return 'fine del ' .. Roman(date1) .. ' sec. e inizio del ' .. Roman(date2) .. ' sec.' end, ja='$date1世紀から$date2世紀への変わり目', ml='$date1 നൂറ്റാണ്ട് അവസാനവും $date2 നൂറ്റാണ്ട് ആദ്യവും', nb=function(date1, date2, state) return 'slutten av det ' .. Ordinal(date1, 'nb') .. ' århundre/begynnelsen av det ' .. Ordinal(date2, 'nb') .. ' århundre' end, nl=function(date1, date2, state) return 'eind $date1e/begin $date2e eeuw' end, nn=function(date1, date2, state) return 'slutten av det ' .. Ordinal(date1, 'nn') .. ' hundreåret/byrjinga av det ' .. Ordinal(date2, 'nn') .. ' hundreåret' end, pl=function(date1, date2, state) return 'przełom ' .. Roman(date1) ..' i ' .. Roman(date2) ..' wieku' end, qu="$date1 ñiqin waranqawatamanta $date2 ñiqin waranqawataman t'ikrasqanpi", ru=function(date1, date2, state) return 'конец ' .. Roman(date1) ..' — начало ' .. Roman(date2) ..' века' end, sk='prelom $date1. a $date2. storočia', sl=function(date1, date2, state) return 'pozno ' .. Ordinal(date1, 'sl') .. '. in zgodnje ' .. Ordinal(date2, 'sl') .. '. stoletje' end, sv=function(date1, date2, state) return '$date1/' .. Ordinal(date2, 'sv') .. ' årtusendets sekelskifte' end, uk=function(date1, date2, state) return 'кін. ' .. Roman(date1) ..' ст. — поч. ' .. Roman(date2) ..' ст.' end, vi='bước sang thế kỷ $date2', ['zh-hans']=function(date1, date2, state) return '$date1世纪末' .. Ordinal(date2, 'zh-hans') .. '世纪初' end, ['zh-hant']=function(date1, date2, state) return '$date1世紀末' .. Ordinal(date2, 'zh-hant') .. '世紀初' end, default=function(date1, date2, state) return {conj='and', adj1 = 'late', unit1='century', era1=state.era1, adj2 = 'early', unit2='century', era2=state.era2} end, }, -- $date1 - original date - 10 -- $date2 - original date -- if era is BC than $date1 and $date2 are swapped ['turn of the decade']={ nDates = 2, ['be-tarask']='канец $date1-х — пачатак $date2-х гг.', bn='$date1-এর দশকের শেষে এবং $date2-এর দশকের শুরুতে', cs=function(date1, date2, state) local decade1 = math.floor((date1 % 100) / 10) local decade2 = math.floor((date2 % 100) / 10) local century1 = math.floor( date1 / 100) + 1 local century2 = math.floor( date2 / 100) + 1 if decade1 == 0 and date1 < date2 then return "přelom 1. a 2. dekády " .. century1 .. ". století" elseif decade2 == 0 and date2 < date1 then return "přelom 9. a 10. dekády " .. century1 .. ". století" elseif (decade1 == 9 and date1 < date2) or (decade2 == 9 and date2 < date1) then return "přelom " .. century1 .. ". a " .. century2 .. ". století" else return "přelom " .. decade1 .. "0. a " .. decade2 .. "0. let " .. century1 .. ". století" end end, de='späte $date1er und frühe $date2er', ckb='کۆتایییەکانی $date1 و سەرەتاکانی $date2', en='late $date1s and early $date2s', ['en-gb']='late $date1s and early $date2s', ['en-ca']='late $date1s and early $date2s', eo='ŝanĝo de la $date1-a al la $date2-a jardekoj', es='finales de los años $date1 y principios de los años $date2', fa='اواخر دههٔ $date1 و اوایل دههٔ $date2', fi='$date1-luvun loppu ja $date2-luvun alku', fr='fin des années $date1 / début des années $date2', hr='kraj $date1-ih i početak $date2-ih', hsb='pózdnje $date1te a zažne $date2te lěta', it='fine degli anni $date1 / inizio degli anni $date2', ja='$date1年代末から$date2年代初頭', ml='$date1കൾ അവസാനവും $date2കൾ ആദ്യവും', nb = 'sent på $date1-tallet og tidlig på $date2-tallet', nl = 'eind $date1e/begin $date2e decennium', nn = 'seint på $date1-talet og tidleg på $date2-talet', pl=function(date1, date2, state) local decade1 = math.floor(date1 % 100) local decade2 = math.floor(date2 % 100) local century1 = Roman(math.floor( date1 / 100) + 1) local century2 = Roman(math.floor( date2 / 100) + 1) if decade1 == 0 and date1 < date2 then return "przełom pierwszej i drugiej dekady " .. century1 .. " wieku" elseif decade1 == 10 and date1 < date2 then return "przełom drugiej dekady i lat 20. " .. century1 .. " wieku" elseif decade1 == 90 and decade2 == 0 then return "przełom ostatniej dekady " .. century1 .. " i pierwszej dekady " .. century2 .. " wieku" else return "przełom lat " .. decade1 .. ". i " .. decade2 .. ". " .. century1 .. " wieku" end end, qu='qhipaq $date1 watakuna ñawpaq $date2 watakunapas', ru='конец $date1-х — начало $date2-х гг.', sv='sena $date1-talet och tidiga $date2-talet', sk=function(date1, date2, state) local decade1 = math.floor((date1 % 100) / 10) local decade2 = math.floor((date2 % 100) / 10) local century1 = math.floor( date1 / 100) + 1 local century2 = math.floor( date2 / 100) + 1 if decade1 == 0 and date1 < date2 then return "prelom 1. a 2. desaťročia " .. century1 .. ". storočia" elseif decade2 == 0 and date2 < date1 then return "prelom 9. a 10. desaťročia " .. century1 .. ". storočia" elseif (decade1 == 9 and date1 < date2) or (decade2 == 9 and date2 < date1) then return "prelom " .. century1 .. ". a " .. century2 .. ". storočia" else return "prelom " .. decade1 .. "0. a " .. decade2 .. "0. rokov " .. century1 .. ". storočia" end end, sl='pozna $date1-ta in zgodnja $date2-ta leta', uk='кінець $date1-х — початок $date2-х рр.', ur='اخیر دہائی $date1 و ابتدا دہائی $date2', vi='bước sang thập niên $date2', ['zh-hans']='$date1年代末$date2年代初', ['zh-hant']='$date1年代末$date2年代初', default=function(date1, date2, state) return {conj='and', adj1 = 'late', unit1='decade', era1=state.era1, adj2 = 'early', unit2='decade', era2=state.era2} end, }, -- $date1 - original date - 1 -- $date2 - original date -- if era is BC than $date1 and $date2 are swapped ['turn of the year']={ nDates = 2, ['be-tarask']='канец $date1 — пачатак $date2 г.', bn='$date1-এর শেষে এবং $date2-এর শুরুতে', ckb='کۆتاییی $date1 و سەرەتای $date2', cs='přelom roku $date1/$date2', de='Jahreswechsel $date1/$date2', en='late $date1 and early $date2', ['en-gb']='late $date1 and early $date2', ['en-ca']='late $date1 and early $date2', eo='jarŝanĝo $date1 / $date2', es='finales de $date1 y principios de $date2', fa='اواخر $date1 و اوایل $date2', fi='vuoden $date1 loppu ja vuoden $date2 alku', fr='fin $date1 / début $date2', hsb='kónc $date1/spočatk $date2', ja='$date1年末から$date2年初め', ml='$date1 അവസാനവും $date2 ആദ്യവും', nb='årsskiftet $date1/$date2', nl='eind $date1 en begin $date2', nn='årsskiftet $date1/$date2', pl='koniec $date1 i początek $date2 roku', qu="$date1 qhipaq p'unchawkuna $date2 ñawpaq p'unchawkunapas", ru='конец $date1 — начало $date2 г.', sk='prelom rokov $date1/$date2', sl='konec leta $date1 in začetek $date2', sv='sena $date1 och tidiga $date2', uk='кінець $date1 — початок $date2 р.', ur='اخیر $date1 و ابتدا $date2', vi='bước sang năm $date2', ['zh-hans']='$date1年末$date2年初', ['zh-hant']='$date1年末$date2年初', default=function(date1, date2, state) return {conj='and', adj1 = 'late', unit1='year', era1=state.era1, adj2 = 'early', unit2='year', era2=state.era2} end, }, ['circa2'] = { nDates = 2, ar='حوالي $date1 – $date2', ['be-tarask']={'прыкладна з $date1 да прыкладна $date2', case={'gen','gen'}}, bn='প্রায় $date1 এবং প্রায় $date2-এর মাঝামাঝি', ca='cap a $date1 o $date2', ckb='بە نزیکی لەنێوان $date1 و $date2', cs='cca $date1 až $date2', da='ca. mellem $date1 og $date2', de='etwa $date1 bis $date2', el='περίπου μεταξύ $date1 και $date2', en='between c. $date1 and c. $date2', ['en-gb']='between circa $date1 and circa $date2', ['en-ca']='between circa $date1 and circa $date2', eo='proksimume de $date1 ĝis $date2', es='hacia $date1 o $date2', et='ca. $date1 ja $date2 vahel', fa='حدوداً بین $date1 و $date2', fi={function(date1, date2, state) return 'suunnilleen ' .. decYear(state,'fi','xxx') .. '$date1 ja $date2 välisenä aikana' end, case={'gen','gen'}}, fr='vers $date1-$date2', gl='entre $date1 e $date2', he='בסביבות ה- $date1 עד $date2', hr='oko između $date1 i $date2', hsb={'wot něhdźe $date1 do něhdźe $date2', case={'gen','gen'}}, hu='$date1 és $date2 között', it='tra il $date1 e il $date2 circa', ja='$date1頃と$date2頃の間', mk='~ помеѓу $date1 и $date2', ml='ഉദ്ദേശം $date1-നും ഉദ്ദേശം $date2-നും ഇടയ്ക്ക്', nb='ca. mellom $date1 og $date2', nn='ca. mellom $date1 og $date2', nds='üm un bi $date1 bet $date2', nl='ca. $date1 tot $date2', no='ca. mellom $date1 og $date2', pl={'od około $date1 do około $date2', case={'gen','gen'}}, pt='cerca de $date1 a $date2', qu="$date1 p'unchawmantachá $date2 p'unchawkamachá", ro='circa $date1 – $date2', ru={'с около $date1 до, примерно, $date2', case={'gen','gen'}}, sk='cca $date1 až $date2', sl={function(date1, date2, state) return 'ok. ' .. decYear(state,'sl','gen') .. ' $date1 do $date2' end, case={'gen','gen'}}, sv='c:a $date1 till $date2', uk={'з близько $date1 до, приблизно, $date2', case={'gen','gen'}}, ur='قریباً بین $date1 و $date2', vi='giữa vào khoảng $date1 và vào khoảng $date2', ['zh-hans']='约$date1~$date2间', ['zh-hant']='約$date1~$date2間', default=function(date1, date2, state) return {conj='between', adj1 = 'circa', unit1=state.unit1, era1=state.era1, adj2 = 'circa', unit2=state.unit2, era2=state.era2} end, }, -- ========================================== -- === Eras and calendars =================== -- ========================================== ['julian'] = { nDates = 2, ar='$date1 ($date2 حسب [[w:ar:التقويم اليولياني|تقويم يولياني]])', ['be-tarask']='$date1 ($date2 паводле [[w:be-tarask:Юліянскі каляндар|ст. ст.]]', bn='$date1 (জুলিয়ান ক্যালেন্ডারে $date2)', ca='$date1 ($date2 segons el [[w:ca:Calendari julià|calendari julià]])', ckb='$date1 ($date2 بەپێی [[w:ڕۆژژمێری جولیانی|ڕۆژژمێری جولیانی]])', cs='$date1 ($date2 podle [[w:cs:Juliánský kalendář|juliánského kalendáře]])', da='$date1 ($date2 [[w:da:Julianske kalender|Julianske kalender]])', de='$date1 ($date2 [[w:de:Julianischer Kalender|Julianischer Kalender]])', el='$date1 ($date2 με το [[w:el:Ιουλιανό ημερολόγιο|Ιουλιανό ημερολόγιο]])', en='$date1 ($date2 in [[w:Old Style and New Style dates|Julian calendar]])', ['en-gb']='$date1 ($date2 in [[w:Old Style and New Style dates|Julian calendar]])', ['en-ca']='$date1 ($date2 in [[w:Old Style and New Style dates|Julian calendar]])', fa='$date1 ($date2 در [[w:fa:Old Style and New Style dates|تقویم جولیان]])', eo='$date1 ($date2 laŭ la [[w:eo:Julia kalendaro|Julia Kalendaro]])', es='$date1 ($date2 según el [[w:es:Calendario juliano|calendario juliano]])', et='$date1 ($date2 [[w:et:Juliuse kalender|Juliuse kalendris]])', fi='$date1 ($date2 [[w:fi:juliaaninen kalenteri|juliaanisen kalenterin]] mukaan)', fr='$date1 ($date2 dans le [[w:fr:calendrier julien|calendrier julien]])', gl='$date1 ($date2 segundo o [[w:gl:Calendario xuliano|calendario xuliano]])', hu='$date1 ($date2 a [[w:hu:Julianus-naptár|Julián naptárban]])', hr='$date1 ($date2 prema [[w:hr:Julijanski kalendar|julijanskom kalendaru]])', he='$date1 ($date2 ב[[w:he:לוח היוליאני|הלוח היוליאני]])', hsb='$date1 ($date2 po julianiskej protyce)', it='$date1 ($date2 secondo il [[w:it:Calendario giuliano|calendario giuliano]])', ja='$date1([[w:ja:ユリウス暦|ユリウス暦]] $date2)', mk='$date1 ($date2 по [[w:mk:Јулијански календар|Јулијанскиот календар]])', ml='$date1 ([[w:ml:ജൂലിയൻ കാലഗണനാരീതി|ജൂലിയൻ വർഷത്തിൽ]] $date2)', ml='$date1 ([[w:Old Style and New Style dates|ജൂലിയൻ കാലഗണനാരീതിയിൽ]] $date2)', nds='$date1 ($date2 na’n [[w:nds:Juliaansch Klenner|Juliaanschen Klenner]])', nb='$date1 ($date2 etter [[w:no:Juliansk kalender|den julianske kalenderen]])', nn='$date1 ($date2 etter [[w:no:Juliansk kalender|den julianske kalenderen]])', nl='$date1 ($date2 volgens de [[w:nl:Juliaanse kalender|Juliaanse kalender]])', no='$date1 ($date2 etter [[w:no:Juliansk kalender|den julianske kalender]])', pl='$date1 ($date2 wedle [[w:pl:Daty nowego i starego porządku|kalendarza juliańskiego]])', pt='$date1 ($date2 no [[w:pt:Mudança para o calendário gregoriano|calendário juliano]])', qu='$date1 ($date2 [[w:qu:Hulyanu kalindaryu|Hulyanu kalindaryupi]])', ro='$date1 ($date2 în [[w:ro:calendarul iulian|calendarul iulian]])', ru='$date1 ($date2 [[w:ru:Юлианский календарь|по ст. ст.]])', sk='$date1 ($date2 podľa [[w:sk:Juliánsky kalendár|juliánskeho kalendára]])', sl='$date1 ($date2 po [[w:sl:Julijanski koledar|julijanskem koledarju]])', sv='$date1 ($date2 [[w:sv:Gamla stilen|gamla stilen]])', uk='$date1 ($date2 [[w:uk:Юліанський календар|Юліанський календар]])', ur='$date1 ($date2 حسب [[w:ur:نئے اور پانے انداز کی تاریخ|جولین تقویم]])', vi='$date1 ($date2 trong [[w:vi:Lịch Julius|lịch Juliêng]])', ['zh-hans']='$date1([[w:Old Style and New Style dates|格里历]]$date2)', ['zh-hant']='$date1([[w:Old Style and New Style dates|格里曆]]$date2)', default='<span style="color:red">$date1 ($date2 in [[w:Old Style and New Style dates|Julian calendar]])</span>', }, ['ad'] = { nDates = 1, ['be-tarask']='$date1 [[w:be-tarask:ад Нараджэньня Хрыста|пасьля Н. Х.]]', bn ='$date1 খ্রিস্টাব্দ', ca ='$date1 dC', ckb ='$date1 [[w:ckb:ئانۆ دۆمینی|پاز]]', cs ='$date1 n. l.', de ='$date1 n. Chr.', el ='$date1 μ.Χ.', en ='$date1 [[w:en:Anno Domini|AD]]', ['en-gb'] ='$date1 [[w:en:Anno Domini|AD]]', ['en-ca'] ='$date1 [[w:en:Anno Domini|AD]]', eo ='$date1 pK', es ='$date1 [[w:es:Anno Dómini|d. C.]]', fa ='$date1 م.', fi ='$date1 jaa.', fr ='$date1 ap. J.-C.', gl ='$date1 d.C.', he ='$date1 לספירה', hu ='i. sz. $date1', hr ='$date1 p. Kr.', hsb='$date1 po Chr.', it ='$date1 d.C.', ja ='西暦$date1', mk ='$date1 н.е.', ml ='ക്രി.വ. $date1', nb ='$date1 e.Kr.', nl ='$date1 n.Chr.', nn ='$date1 e.Kr.', no ='$date1 e.Kr.', pl ='$date1 [[w:pl:naszej ery|n.e.]]', ro ='$date1 d.Hr.', ru ='$date1 н. э.', sk ='$date1 po Kr.', sl ='$date1 n. št.', sv ='$date1 [[w:sv:Efter Kristus|e.Kr.]]', uk ='$date1 н. е.', ur ='$date1 ء', vi ='$date1 [[w:vi:Công Nguyên|CN]]', ['zh-hans'] ='[[w:zh:基督纪年|公元]]$date1', ['zh-hant'] ='[[w:zh:基督纪年|公元]]$date1', default='<span style="color:red">$date1 [[w:en:Anno Domini|AD]]</span>', }, ['ah'] = { nDates = 1, ['be-tarask']='$date1 [[w:be-tarask:Ісламскі каляндар|хіджры]]', bn ='$date1 [[w:bn:ইসলামি বর্ষপঞ্জি|হিজরী]]', ckb ='$date1 [[w:ckb:ساڵی کۆچی|سک]]', cs ='$date1 [[w:cs:Anno Hegirae|AH]]', en ='$date1 [[w:en:Hijri year|AH]]', ['en-gb'] ='$date1 [[w:en:Hijri year|AH]]', ['en-ca'] ='$date1 [[w:en:Hijri year|AH]]', eo ='$date1 post la [[w:eo:Heĝiro|Heĝiro]]', fa ='$date1 ه.ق.', fr ='$date1 [[w:fr:Calendrier musulman|A.H.]]', he ="$date1 [[w:he:הלוח המוסלמי|להג'רה]]", hr ='$date1 god. hidžre', ja ='[[w:ja:ヒジュラ暦|ヒジュラ暦]]$date1', ml ='$date1 [[w:ml:ഇസ്‌ലാമിക കലണ്ടർ|ഹിജ്റ]]', nb ='$date1 [[w:no:Den muslimske kalenderen|A.H.]]', nn ='$date1 [[w:nn:Den muslimske kalenderen|A.H.]]', pl ='$date1 [[w:pl:Kalendarz muzułmański|A.H.]]', ru ='$date1 [[w:ru:Исламский календарь|хиджры]]', sk ='$date1 [[w:sk:A. H.|A. H.]]', sv ='$date1 [[w:sv:Muslimska kalendern|A.H.]]', uk ='$date1 [[w:uk:Мусульманський календар|хіджри]]', ur ='$date1 ھ', vi ='$date1 [[w:vi:Lịch Hồi giáo|AH]]', ['zh-hans'] ='[[w:en:Hijri year|Hijri历]]$date1', ['zh-hant'] ='[[w:en:Hijri year|Hijri曆]]$date1', default='<span style="color:red">$date1 [[w:en:Hijri year|AH]]</span>', }, ['bc'] = { nDates = 1, ar ='$date1 ق.م.', ['be-tarask']='$date1 [[w:be-tarask:да Нараджэньня Хрыста|да Н. Х.]]', bn ='খ্রিস্টপূর্ব $date1', ca ='$date1 [[w:ca:Abans de Crist|aC]]', ckb ='$date1 [[w:ckb:پێش زایین|پز]]', cs ='$date1 [[w:cs:Př. n. l.|př. n. l.]]', de ='$date1 [[w:de:v. Chr.|v. Chr.]]', el ='$date1 π.Χ.', en ='$date1 [[w:en:Ante Christum Natum|BC]]', ['en-gb'] ='$date1 [[w:en:Ante Christum Natum|BC]]', ['en-ca'] ='$date1 [[w:en:Ante Christum Natum|BC]]', eo ='$date1 aK', es ='$date1 [[w:es:Antes de Cristo|a. C.]]', fa ='$date1 پ.م.', fi ='$date1 eaa.', fr ='$date1 av. J.-C.', gl ='$date1 [[w:gl:Antes de Cristo|a.C.]]', he ='$date1 לפני הספירה', hr ='$date1 [[w:hr:Pr. Kr.|pr. Kr.]]', hsb='$date1 do Chr.', hu ='i. e. $date1', it ='$date1 [[w:it:Avanti Cristo|a.C.]]', ja ='[[w:ja:紀元前|紀元前]]$date1', mk ='$date1 п.н.е.', ml ='ക്രി.മു. $date1', nds='$date1 vör uns Tied', nb ='$date1 f.Kr.', nl ='$date1 v.Chr.', nn ='$date1 f.Kr.', no ='$date1 f.Kr.', pl ='$date1 [[w:pl:przed naszą erą|p.n.e.]]', ro ='$date1 î.Hr.', ru ='$date1 до н. э.', sk ='$date1 [[w:sk:Pred Kristom|pred Kr.]]', sl ='$date1 pr. n. št.', sv ='$date1 [[w:sv:Före Kristus|f.Kr.]]', uk ='$date1 до н. е.', ur ='$date1 ق م', vi ='$date1 [[w:vi:Trước Công Nguyên|TCN]]', ['zh-hans'] ='[[w:zh:基督纪年|公元前]]$date1', ['zh-hant'] ='[[w:zh:基督纪年|公元前]]$date1', default='<span style="color:red">$date1 [[w:en:Ante Christum Natum|BC]]</span>', }, ['bp'] = { nDates = 1, ar ='$date1 [[w:ar:قبل الحاضر|قبل الحاضر]]', be ='$date1 [[w:be-tarask:Да нашых дзён|Да нашых дзён]]', ['be-tarask']='$date1 [[w:be-tarask:Да нашых дзён|Да нашых дзён]]', bn ='বর্তমানের আগে $date1', ca ='$date1 [[w:ca:BP (unitat de temps)|BP]]', ckb ='$date1 [[w:ckb:پێش ئێستا|پێش ئێستا]]', cs ='$date1 [[w:cs:B. P.|B. P.]]', de ='$date1 [[w:de:Before Present|BP]]', el ='$date1 [[w:en:Before Present|BP]]', en ='$date1 [[w:en:Before Present|BP]]', ['en-gb'] ='$date1 [[w:en:Before Present|BP]]', ['en-ca'] ='$date1 [[w:en:Before Present|BP]]', eo='$date1 [[w:eo:Before Present|BP]]', es ='$date1 [[w:es:Before Present|BP]]', et ='$date1 [[w:et:Before Present|BP]]', fa ='$date1 پیش از اکنون', fi ='$date1 [[w:fi:BP (ajanlasku)|BP]]', fr ='$date1 [[w:fr:Avant le présent|avant le présent]]', hr ='$date1 [[w:hr:BP|prije današnjice]]', it ='$date1 [[w:it:Before Present|BP]]', ja ='$date1 [[w:ja:BP (年代測定)|BP]]', lv ='$date1 [[w:lv:Pirms mūsdienām|Pirms mūsdienām]]', mk ='$date1 [[w:mk:Пред сегашноста|п.с.]]', ml ='[[w:en:Before Present|ഇന്നത്തെ കാലത്തിനു മുമ്പ്]] $date1', nb ='$date1 [[w:no:Before Present|BP]]', nn ='$date1 [[w:nn:Before Present|BP]]', nl ='$date1 [[w:nl:Before Present|BP]]', no ='$date1 [[w:no:Before Present|BP]]', pl ='$date1 [[w:pl:Before Present|BP]]', pt ='$date1 [[w:pt:Antes do Presente|Antes do Presente]]', ru ='$date1 [[w:ru:До настоящего времени|до н. в.]]', sk ='$date1 [[w:sk:BP (čas)|BP]]', sl ='$date1 [[w:sl:pred sedanjostjo|p. s.]]', sr ='$date1 [[w:sr:Прије садашњости|Прије садашњости]]', sv ='$date1 [[w:sv:B.P.|B.P.]]', uk ='$date1 [[w:uk:Років тому|Років тому]]', ur ='$date1 قبل حاضر', vi ='$date1 [[w:en:Before Present|trước ngày nay]]', ['zh-hans'] ='[[w:zh:距今|距今]]$date1', ['zh-hant'] ='[[w:zh:距今|距今]]$date1', default='<span style="color:red">$date1 [[w:en:Before Present|BP]]</span>', }, } -- List of possible string inputs to "adj", "junction", "era", and "precision" fields -- Each term is matched to its synonym and the role: a - adjective, e - era -- p - precision, c - calendar and combos, r - certainty and j - conjunction p.Synonyms = { -- adjectives and prepositions ['quarter1']={'1quarter','a'}, ['1stquarter']={'1quarter','a'}, ['1st quarter']={'1quarter','a'}, ['first quarter']={'1quarter','a'}, ['1quarter']={'1quarter','a'}, ['quarter2']={'2quarter','a'}, ['2ndquarter']={'2quarter','a'}, ['2nd quarter']={'2quarter','a'}, ['second quarter']={'2quarter','a'}, ['2quarter']={'2quarter','a'}, ['quarter3']={'3quarter','a'}, ['3rdquarter']={'3quarter','a'}, ['3rd quarter']={'3quarter','a'}, ['third quarter']={'3quarter','a'}, ['3quarter']={'3quarter','a'}, ['quarter4']={'4quarter','a'}, ['4thquarter']={'4quarter','a'}, ['4th quarter']={'4quarter','a'}, ['fourth quarter']={'4quarter','a'}, ['4quarter']={'4quarter','a'}, ['spring']={'spring','a'}, ['summer']={'summer','a'}, ['fall']={'autumn','a'}, ['autumn']={'autumn','a'}, ['winter']={'winter','a'}, ['beginning']={'early','a'}, ['early']={'early','a'}, ['1half']={'firsthalf','a'}, ['1sthalf']={'firsthalf','a'}, ['1st half']={'firsthalf','a'}, ['firsthalf']={'firsthalf','a'}, ['first half']={'firsthalf','a'}, ['end']={'late','a'}, ['late']={'late','a'}, ['middle']={'mid','a'}, ['mid']={'mid','a'}, ['2half']={'secondhalf','a'}, ['2ndhalf']={'secondhalf','a'}, ['2nd half']={'secondhalf','a'}, ['secondhalf']={'secondhalf','a'}, ['second half']={'secondhalf','a'}, ['<']={'before','a'}, ['b']={'before','a'}, ['before']={'before','a'}, ['>']={'after','a'}, ['a']={'after','a'}, ['after']={'after','a'}, ['~']={'circa','a'}, ['c']={'circa','a'}, ['ca']={'circa','a'}, ['circa']={'circa','a'}, ['by']={'by','a'}, ['from']={'from','a'}, ['until']={'until','a'}, ['uncertain']={'uncertain','r'}, ['possibly']={'possibly','r'}, ['probably']={'probably','r'}, ['presumably']={'presumably','r'}, -- eras ['bc']={'bc','e'}, ['bce']={'bc','e'}, ['before present']={'bp','e'}, ['bp']={'bp','e'}, ['ce']={'ad','e'}, ['ad']={'ad','e'}, ['ah']={'ah','e'}, -- conjunctions ['&']={'and','j'}, ['and']={'and','j'}, ['or']={'or','j'}, ['-']={'from-until','j'}, ['–']={'from-until','j'}, ['from-until']={'from-until','j'}, ['between']={'between','j'}, ['circa2']={'circa2','a'}, -- precision ['day']={'day','p'}, ['month']={'month','p'}, ['year']={'year','p'}, ['s']={'decade','p'}, ['decade']={'decade','p'}, ['century']={'century','p'}, ['millennium']={'millennium','p'}, ['million']={'million','p'}, ['billion']={'billion','p'}, -- calendar ['julian']={'julian','c'}, ['islamic']={'islamic','c'}, ['hijri']={'islamic','c'}, -- "Turn of the ..." options are a combos which will be handled in similar way as calendars ['turn of the century']={'turn of the century','c'}, ['century turn']={'turn of the century','c'}, ['turn of the decade']={'turn of the decade','c'}, ['decade turn']={'turn of the decade','c'}, ['turn of the year']={'turn of the year','c'}, ['year turn']={'turn of the year','c'}, -- other dates which will be handled in similar way as calendars ['?']={'unknown','c'}, ['unknown']={'unknown','c'}, ['unknown year']={'year unknown','c'}, ['year unknown']={'year unknown','c'}, } return p dq6g8vtfaua8zgt63ytoeyms0jwcsm1 803612 803611 2026-07-30T00:19:31Z SM7 3953 6 revisions imported from [[:en:Module:I18n/complex_date]] 803611 Scribunto text/plain --[[ __ __ _ _ ___ _ ___ __ _ _ _ | \/ | ___ __| |_ _| | ___ _|_ _/ |( _ ) _ __ / /__ ___ _ __ ___ _ __ | | _____ __ __| | __ _| |_ ___ | |\/| |/ _ \ / _` | | | | |/ _ (_)| || |/ _ \| '_ \ / / __/ _ \| '_ ` _ \| '_ \| |/ _ \ \/ / / _` |/ _` | __/ _ \ | | | | (_) | (_| | |_| | | __/_ | || | (_) | | | |/ / (_| (_) | | | | | | |_) | | __/> < | (_| | (_| | || __/ |_| |_|\___/ \__,_|\__,_|_|\___(_)___|_|\___/|_| |_/_/ \___\___/|_| |_| |_| .__/|_|\___/_/\_\ \__,_|\__,_|\__\___| |_| This code was copied from Commons please request changes there. Maintainers: *Jarekt ]] local function Ordinal(...) return require('Module:Ordinal-cd')._Ordinal(...) end local function Roman(...) return require('Module:Roman-cd')._Numeral(...) end -- ========================================================================== -- === Local Helper functions ============================================== -- ========================================================================== -- form of French "of" based on "precision", which is wikibase compatible date precision format: -- 6=millennium, 7=century, 8=decade, 9=year, 10=month, 11=day -- 1 2 3 4 5 mill cent deca year month day local frOf = {'du','du','du','du','du', 'du', 'du', 'de', 'de', 'de', 'du'} local function decYear(state,lang,case) --Declension of word "year" in several languages if state.precision == 9 then yearCase = { fi={xxx='vuosien', gen='vuoden', ill='vuoteen', ptv='vuotta', ela='vuodesta', ine='vuonna'}, sl={insP='letoma', ins='letom', gen='leta', loc='letu'}, hsb={insP='lětami', ins='lětom', gen='lěta', loc='lěće'} } return yearCase[lang][case] .. ' ' else return '' end end local function decCentury(lang,case) --Declension of word "century" in several languages centuryCase = { pl={nom='wiek', gen='wieku', ins='wiekiem', loc='wieku', default = 'wiek'}, hsb={nom='lětstotk', gen='lětstotka', ins='lětstotkom', loc='lětstotku', default = 'lětstotk'} } return centuryCase[lang][case] or centuryCase[lang]['default'] end local function decMillennium(lang,case) --Declension of word "century" in several languages millenniumCase = { pl={nom='tysiąclecie', gen='tysiąclecia', ins='tysiącleciem', loc='tysiącleciu', default = 'tysiąclecie'}, hsb={nom='lěttysac', gen='lěttysaca', ins='lěttysacom', loc='lěttysacu', default = 'lěttysac'} } return millenniumCase[lang][case] or millenniumCase[lang]['default'] end -- ========================================================================== -- === Output data structure =============================================== -- ========================================================================== local p = {} --[[ Multiple formats are possible: 1) string with key words "$date1" and "$date2" which will be replaced later by the date strings 2) function which can be used to create output string * Function INPUTS: - date1 and date2 string with date strings. "$date1" and "$date2" can also be used which will be replaced latter - state is a table with full description of the phrase we are trying to construct. It includes state.precision1 (and 2) which is wikibase compatible date precision format: 6=millennium, 7=century, 8=decade, 9=year, 10=month, 11=day 3) table with following fields: * table[1] is either string or a function * table.case is a 2 element table with Grammatical cases used by given language to alter month name or other adjective related to date # 1 and 2 ]] p.Translations = { -- ========================================== -- === Two date phrases ===================== -- ========================================== ['from-until'] = { nDates = 2, ar='من $date1 إلى $date2', ['be-tarask']={'з $date1 да $date2', case={'gen','gen'}}, bn='$date1 থেকে $date2 পর্যন্ত', ca='$date1 fins $date2', ckb='لە $date1 تاوەکوو $date2', cs='$date1 až $date2', da='fra $date1 til $date2', de='$date1 bis $date2', el='από $date1 έως $date2', en='from $date1 until $date2', ['en-gb']='from $date1 until $date2', ['en-ca']='from $date1 until $date2', eo='de $date1 al $date2', es='de $date1 a $date2', et='$date1 kuni $date2', fa='از $date1 تا $date2', fi='$date1–$date2', -- välisenä aikana fr=function(date1, date2, state) -- could actually be better should be "d'octobre à mars 2013' not "de octore 2013 à mars 2013" if state.precision > 10 then -- precision > month return 'du $date1 au $date2' else -- "DE septebmbre" / "D'octobre" return require("Module:Linguistic").of(date1, 'fr') .. " à $date2" end end, gl='de $date1 a $date2', --Template:From until used 'desde $date1 ata $date2' he='מ- $date1 עד $date2', hr='od $date1 do $date2', hsb={'wot $date1 do $date2', case={'gen','gen'}}, hu='$date1 – $date2', it='dal $date1 al $date2', ja='$date1から$date2まで', mk='од $date1 до $date2', ml='$date1 മുതൽ $date2 വരെ', nb='fra $date1 til $date2', nds='$date1 bet $date2', nn='fra $date1 til $date2', nl='$date1 tot $date2', no='fra $date1 til $date2', pl={'od $date1 do $date2', case={'gen','gen'}}, ro='din $date1 până în $date2', pt='de $date1 a $date2', qu="$date1 p'unchawmanta $date2 p\'unchawkama", ru={'с $date1 по $date2', case={'gen','gen'}}, sk={'od $date1 do $date2', case={'gen','gen'}}, sl={'od $date1 do $date2', case={'gen','gen'}}, sv='från $date1 till $date2', uk={'з $date1 до $date2', case={'gen','gen'}}, ur='از $date1 تا $date2', vi='từ $date1 đến $date2', ['zh-hans']='从$date1到$date2', ['zh-hant']='從$date1到$date2', default='<span style="color:red">$date1 - $date2</span>', }, ['between']={ nDates = 2, ar='بين $date1 و $date2', ['be-tarask']={'між $date1 і $date2', case={'ins','ins'}}, bn='$date1 এবং $date2-এর মাঝামাঝি', ca='entre $date1 i $date2', ckb='لەنێوان $date1 و $date2', cs={'mezi $date1 a $date2', case={'ins','ins'}}, da='mellem $date1 og $date2', de='zwischen $date1 und $date2', el='μεταξύ $date1 και $date2', en='between $date1 and $date2', ['en-gb']='between $date1 and $date2', ['en-ca']='between $date1 and $date2', eo='inter $date1 kaj $date2', es='entre $date1 y $date2', et='$date1 ja $date2 vahel', fa='بین $date1 و $date2', fi={function(date1, date2, state) return decYear(state,'fi','xxx') .. '$date1 ja $date2 välisenä aikana' end, case={'gen','gen'}}, fr='entre $date1 et $date2', gl='entre $date1 e $date2', he='בין $date1 ו- $date2', hr='između $date1 i $date2', hsb={'mjez $date1 a $date2', case={'ins','ins'}}, hu='$date1 és $date2 között', hi='$date1 और $date2 के बीच', it='tra il $date1 e il $date2', ja='$date1と$date2の間', mk='помеѓу $date1 и $date2', ml='$date1, $date2 എന്നിവയ്ക്കിടയ്ക്ക്', nb='mellom $date1 og $date2', nds='twischen $date1 un $date2', nn='mellom $date1 og $date2', nl='tussen $date1 en $date2', no='mellom $date1 og $date2', pl={'między $date1 a $date2', case={'ins','ins'}}, ro='între $date1 și $date2', pt='entre $date1 e $date2', qu="$date1 p'unchawpa $date2 p'unchawpapas chawpin", ru={'между $date1 и $date2', case={'ins','ins'}}, sk={'medzi $date1 a $date2', case={'ins','ins'}}, sl={function(date1, date2, state) return 'med ' .. decYear(state,'sl','insP') .. '$date1 in $date2' end, case={'ins','ins'}}, sv='mellan $date1 och $date2', uk={'між $date1 та $date2', case={'ins','ins'}}, ur='بین $date1 و $date2', vec='tra el $date1 e el $date2', vi='giữa $date1 và $date2', ['zh-hans']='在$date1到$date2之间', ['zh-hant']='在$date1到$date2之間', default='<span style="color:red">$date1 - $date2</span>', }, ['or'] = { nDates = 2, ar='$date1 أو $date2', ['be-tarask']='$date1 ці $date2', bn='$date1 অথবা $date2', ca='$date1 o $date2', ckb='$date1 یان $date2', cs='$date1 nebo $date2', da='$date1 eller $date2', de='$date1 oder $date2', el='$date1 ή $date2', en='$date1 or $date2', ['en-gb']='$date1 or $date2', ['en-ca']='$date1 or $date2', eo='$date1 aŭ $date2', es='$date1 o $date2', et='$date1 või $date2', fa='$date1 یا $date2', fi='$date1 tai $date2', fr='$date1 ou $date2', gl='$date1 ou $date2', he='$date1 או $date2', hr='$date1 ili $date2', hsb='$date1 abo $date2', hu='$date1 vagy $date2', it='$date1 o $date2', ja='$date1または$date2', mk='$date1 или $date2', ml='$date1 അഥവാ $date2', nb='$date1 eller $date2', nn='$date1 eller $date2', nds='$date1 oder $date2', nl='$date1 of $date2', no='$date1 eller $date2', pl='$date1 lub $date2', pt='$date1 ou $date2', qu='$date1 icha $date2', ro='$date1 sau $date2', ru='$date1 или $date2', sk='$date1 alebo $date2', sl='$date1 ali $date2', sv='$date1 eller $date2', uk='$date1 або $date2', ur='$date1 یا $date2', th='$date1 หรือ $date2', vi='$date1 hoặc $date2', ['zh-hans']='$date1或$date2', ['zh-hant']='$date1或$date2', default=function(date1, date2, state) return require("Module:linguistic").conj({date1, date2}, state.lang, 'or') end, }, ['and'] = { nDates = 2, ar='$date1 و $date2', ['be-tarask']='$date1 і $date2', bn='$date1 এবং $date2', ca='$date1 i $date2', ckb='$date1 و $date2', cs='$date1 a $date2', da='$date1 og $date2', de='$date1 und $date2', el='$date1 και $date2', en='$date1 and $date2', ['en-gb']='$date1 and $date2', ['en-ca']='$date1 and $date2', eo='$date1 kaj $date2', es='$date1 y $date2', et='$date1 ja $date2', fa='$date1 و $date2', fi='$date1 ja $date2', fr='$date1 et $date2', gl='$date1 e $date2', he='$date1 ו- $date2', hu='$date1 és $date2', hsb='$date1 a $date2', it='$date1 e $date2', ja='$date1および$date2', mk='$date1 и $date2', ml='$date1 ഒപ്പം $date2', nb='$date1 og $date2', nn='$date1 og $date2', nds='$date1 un $date2', nl='$date1 en $date2', no='$date1 og $date2', pl='$date1 i $date2', pt='$date1 e $date2', qu='$date1wan $date2pas', ro='$date1 și $date2', ru='$date1 и $date2', sk='$date1 a $date2', sl='$date1 in $date2', sv='$date1 och $date2', uk='$date1 та $date2', ur='$date1 و $date2', vi='$date1 và $date2', ['zh-hans']='$date1和$date2', ['zh-hant']='$date1和$date2', default=function(date1, date2, state) return require("Module:linguistic").conj({date1, date2}, state.lang, 'and') end, }, -- ========================================== -- === Prepositions ========================= -- ========================================== ['by'] = { nDates = 1, ar='حوالي $date1', ['be-tarask']={'да $date1', case={'gen',''}}, bn='$date1 কর্তৃক', ca='vers $date1', ckb='نزیکی $date1', cs={'kolem $date1', case={'gen',''}}, da='mod $date1', de='spätestens $date1', el='έως $date1', en='by $date1', ['en-gb']='by $date1', ['en-ca']='by $date1', eo='ne post $date1', es='para $date1', et='hiljemalt $date1', fa='توسط $date1', fi={function(date1, state) return decYear(state,'fi','ill') .. '$date1 mennessä' end, case={'ill',''}}, fr='$date1 (au plus tard)', gl='cara a $date1', he='עד $date1', hr='do $date1', hsb={'do $date1', case={'gen',''}}, it='entro il $date1', ja='$date1まで', mk='кон $date1', ml='$date1-ൽ', nb='innen $date1', nn='innan $date1', nds='$date1 oder vördem al', nl='tegen $date1', no='innen $date1', pl={'przed $date1', case={'ins',''}}, pt='por $date1', qu="$date1 p'unchawkama", ro='de $date1', ru={'к $date1', case={'dat',''}}, sk={'okolo $date1', case={'gen',''}}, sl={function(date1, state) return 'najpozneje ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='senast $date1', uk={'до $date1', case={'dat',''}}, ur='توسط $date1', vi='vào $date1', -- need to insert "đã" somewhere later in the sentence --> ['zh-hans']='$date1前后', ['zh-hant']='$date1前后', default='<span style="color:red">by $date1</span>', }, ['before'] = { nDates = 1, ar='قبل $date1', ['be-tarask']={'перад $date1', case={'ins',''}}, bn='$date1-এর আগে', bg='Преди $date1', ca='abans $date1', ckb='پێش $date1', cs={'před $date1', case={'ins',''}}, da='før $date1', de='vor $date1', el='πριν από $date1', en='before $date1', ['en-gb']='before $date1', ['en-ca']='before $date1', eo='antaŭ $date1', es='antes de $date1', et='enne $date1', fa='پیش از $date1', fi={function(date1, state) return 'ennen ' .. decYear(state,'fi','ptv') .. '$date1' end, case={'ptv',''}}, fr='avant $date1', gl='antes do $date1', he='לפני $date1', hr='prije $date1', hsb={'do $date1', case={'gen',''}}, hu='$date1 előtt', it='prima del $date1', ja='$date1より前', mk='пред $date1', ml='$date1-നു മുമ്പ്', nb='før $date1', nn='før $date1', nds='vör $date1', nl='voor $date1', no='før $date1', pl={'przed $date1', case={'ins',''}}, pt='antes de $date1', qu="$date1 p'unchaw ñawpan", ro='înainte de $date1', ru={'ранее $date1', case={'gen',''}}, sk={'pred $date1', case={'ins',''}}, sl={function(date1, state) return 'pred ' .. decYear(state,'sl','ins') .. ' $date1' end, case={'ins',''}}, sv='före $date1', uk={'до $date1', case={'gen',''}}, ur='قبل $date1', vi='trước $date1', ['zh-hans']='在$date1之前', ['zh-hant']='在$date1之前', default='<span style="color:red">before $date1</span>', }, ['from'] = { nDates = 1, ar='من $date1', ['be-tarask']={'з $date1', case={'gen',''}}, bn='$date1 থেকে', ckb='لە $date1', cs={'od $date1', case={'gen',''}}, de='Seit $date1', el='από $date1', en='from $date1', ['en-gb']='from $date1', ['en-ca']='from $date1', eo='ekde $date1', es='desde $date1', et='alates $date1', fa='از $date1', fi={function(date1, state) return decYear(state,'fi','ela') .. '$date1 alkaen' end, case={'ela',''}}, fr= function(date1, state) return 'à partir '.. frOf[state.precision] ..' $date1' end, gl='desde o $date1', he='מ- $date1', hr='od $date1', hsb={'wot $date1', case={'gen',''}}, it='dal $date1', ja='$date1から', nds='vonaf $date1', mk='од $date1', nb='fra $date1', nn='frå $date1', ml='$date1 മുതൽ', nl='vanaf $date1', no='fra $date1', pl={'od $date1', case={'gen',''}}, qu="$date1 p'unchawmanta", ro='din $date1', ru={'С $date1', case={'gen',''}}, sk={'od $date1', case={'gen',''}}, sl={function(date1, state) return 'od ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='från $date1', uk={'з $date1', case={'gen',''}}, ur='از $date1', vi='từ $date1', ['zh-hans']='自$date1', ['zh-hant']='自$date1', default='<span style="color:red">from $date1</span>', }, ['until'] = { nDates = 1, ['be-tarask']={'усутыч да $date1', case={'gen',''}}, bn='$date1 পর্যন্ত', ckb='تاوەکوو $date1', cs={'do $date1', case={'gen', ''}}, de='bis $date1', en='until $date1', ['en-gb']='until $date1', ['en-ca']='until $date1', eo='ĝis $date1', es='hasta $date1', fr=function(date1, state) if state.precision > 10 then -- precision > month return "jusqu'au $date1" else return "jusqu'à $date1" end end, hr='do $date1', hsb={'do $date1', case={'gen',''}}, it='al $date1', nb='til $date1', nl='tot $date1', nn='til $date1', ml='$date1 വരെ', pl={'do $date1', case={'gen',''}}, ru={'вплоть до $date1', case={'gen',''}}, sk={'do $date1', case={'gen', ''}}, uk={'аж до $date1', case={'gen',''}}, ur='تک $date1', ['zh-hans']='直到$date1', ['zh-hant']='直到$date1', default='<span style="color:red">until $date1</span>', }, ['after'] = { nDates = 1, ar='بعد $date1', ['be-tarask']={'пасьля $date1', case={'gen',''}}, bn='$date1-এর পরে', ca='després de $date1', ckb='دوای $date1', cs={'po $date1', case={'loc',''}}, de='nach $date1', da='efter $date1', el='μετά από $date1', en='after $date1', ['en-gb']='after $date1', ['en-ca']='after $date1', eo='post $date1', es='después de $date1', et='pärast $date1', fa='پس از $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 jälkeen' end, case={'gen',''}}, fr='après $date1', gl='despois do $date1', he='לאחר $date1', hr='nakon $date1', hu='$date1 után', hsb={'po $date1', case={'loc',''}}, it='dopo il $date1', ja='$date1より後', mk='по $date1', ml='$date1-നു ശേഷം', nb='etter $date1', nn='etter $date1', nds='na $date1', nl='na $date1', no='etter $date1', pl={'po $date1', case={'loc',''}}, pt='após $date1', qu="$date1 p'unchaw qhipan", ro='după $date1', ru={'после $date1', case={'gen',''}}, sk={'po $date1', case={'loc',''}}, sl={function(date1, state) return 'po ' .. decYear(state,'sl','loc') .. ' $date1' end, case={'loc',''}}, sv='efter $date1', uk={'після $date1', case={'gen',''}}, ur='بعد $date1', vi='sau $date1', ['zh-hans']='在$date1之后', ['zh-hant']='在$date1之後', default='<span style="color:red">after $date1</span>', }, -- ========================================== -- === Adjectives =========================== -- ========================================== ['early'] = { nDates = 1, ar='بداية $date1', ['be-tarask']={'пачатак $date1', case={'gen',''}}, bn='$date1-এর শুরুতে', ca='al principi de $date1', ckb='سەرەتای $date1', cs={'začátek $date1', case={'gen',''}}, da='tidlig $date1', de='Anfang $date1', el='αρχές του $date1', en='early $date1', ['en-gb']='early $date1', ['en-ca']='early $date1', eo='komence de $date1', es='a principios de $date1', et='$date1 algus', fa='اوایل $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 alku' end, case={'gen',''}}, fr='début $date1', gl='a principios de $date1', he='תחילת $date1', hr='početak $date1', hsb={'spočatk $date1', case={'gen',''}}, hu='$date1 elején', it='inizio del $date1', ja='$date1初頭', mk='почеток на $date1', ml='$date1 ആദ്യപാദം', nb='tidlig $date1', nn='tidleg $date1', nds='Anfang $date1', nl='begin $date1', no='tidlig $date1', pl={'początek $date1', case={'gen',''}}, pt='a princípios de $date1', qu="$date1 ñawpaq p'unchawkunapi", ro='începând cu $date1', ru={'начало $date1', case={'gen',''}}, sk={'začiatok $date1', case={'gen',''}}, sl=function(date1, state)-- could actually be better should be "d'octobre à mars 2013' not "de octore 2013 à mars 2013" if state.precision == 9 then -- precision > month return 'zgodnje leto $date1' else -- "DE septebmbre" / "D'octobre" return 'zgodnji $date1' end end, sv='början av $date1', uk={'початок $date1', case={'gen',''}}, ur='ابتدا $date1', vi='đầu năm $date1', ['zh-hans']='$date1早期', ['zh-hant']='$date1早期', default='<span style="color:red">early $date1</span>', }, ['mid'] = { nDates = 1, ar='منتصف $date1', ['be-tarask']={'сярэдзіна $date1', case={'gen',''}}, bn='$date1-এর মাঝামাঝি', ca='a mitjan $date1', ckb='ناوەڕاستی $date1', cs={'polovina $date1', case={'gen',''}}, da='midten af $date1', de='Mitte $date1', el='μέσα του $date1', en='mid $date1', ['en-gb']='mid $date1', ['en-ca']='mid $date1', eo='meze de $date1', es='a mediados de $date1', et='$date1 keskel', fa='اواسط $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 puoliväli' end, case={'gen',''}}, fr='mi $date1', gl='a mediados de $date1', he='אמצע $date1', hr='sredina $date1', hsb={'srjedź $date1', case={'gen',''}}, hu='$date1 közepén', it='metà del $date1', ja='$date1半ば', mk='средина на $date1', ml='$date1 മദ്ധ്യം', nb='midten av $date1', nn='midten av $date1', nds='Midd $date1', nl='midden $date1', no='midten av $date1', pl={'środek $date1', case={'gen',''}}, pt='meados de $date1', qu="$date1 chawpin p'unchawkunapi", ro='mijlocul lui $date1', ru={'середина $date1', case={'gen',''}}, sk={'polovica $date1', case={'gen',''}}, sl={function(date1, state) return 'sredina ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='mitten av $date1', uk={'середина $date1', case={'gen',''}}, ur='وسط $date1', vi='giữa năm $date1', ['zh-hans']='$date1中期', ['zh-hant']='$date1中期', default='<span style="color:red">mid $date1</span>', }, ['late'] = { nDates = 1, ar='نهاية $date1', bn='$date1-এর শেষে', ['be-tarask']={'канец $date1', case={'gen',''}}, ca='al final de $date1', ckb='کۆتاییی $date1', cs='konec $date1', da='sen $date1', de='Ende $date1', el='τέλη του $date1', en='late $date1', ['en-gb']='late $date1', ['en-ca']='late $date1', eo='fine de $date1', es='a fines de $date1', et='$date1 lõpp', fa='اواخر $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 loppu' end, case={'gen',''}}, fr='fin $date1', gl='a finais de $date1', he='סוף $date1', hr='kraj $date1', hsb={'kónc $date1', case={'gen',''}}, hu='$date1 végén', it='fine del $date1', ja='$date1末', mk='крајот на $date1', ml='$date1 അന്ത്യപാദം', nb='sent $date1', no='seint $date1', nds='Enn $date1', nl='eind $date1', no='sent $date1', pl={'koniec $date1', case={'gen',''}}, pt='ao final de $date1', qu="$date1 qhipaq p'unchawkunapi", ro='sfârșitul lui $date1', ru={'конец $date1', case={'gen',''}}, sk={'koniec $date1', case={'gen',''}}, sl={function(date1, state) return 'pozno ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='slutet av $date1', uk={'кінець $date1', case={'gen',''}}, ur='اخیر $date1', vi='cuối năm $date1', ['zh-hans']='$date1晚期', ['zh-hant']='$date1晚期', default='<span style="color:red">late $date1</span>', }, ['circa'] = { nDates = 1, ar='حوالي $date1', ['be-tarask']={'каля $date1', case={'gen',''}}, bn='প্রায় $date1', ckb='بەنزیکی $date1', ca='cap a $date1', cs='cca $date1', da='ca. $date1', de='etwa $date1', el='περ. $date1', en='c. $date1', ['en-gb']='circa $date1', ['en-ca']='circa $date1', eo='ĉirkaŭ $date1', es='hacia $date1', -- suggestion use "hacia"/"hacia el" or leave the latin circa or ca. et='ca. $date1', fa='حوالی $date1', -- template:Circa used 'حدود $date1' fi={function(date1, state) return 'suunnilleen ' .. decYear(state,'fi','ine') .. '$date1' end, case={'ine',''}}, -- template:Circa used 'noin $date1' fr=function(date1, state) if state.precision >= 11 then -- precision >= day return 'vers le $date1' else return 'vers $date1' end end, gl='entre $date1', -- template:Circa used 'cara a $date1' he='בסביבות $date1', hr='oko $date1', hsb={'wokoło $date1', case={'gen',''}}, hu='$date1 körül', it='$date1 circa', ja='$date1頃', ko='$date1경', mk='~ $date1', -- template:Circa used 'o. $date1' ml= 'ഉദ്ദേശം $date1', nb='ca. $date1', nn='ca. $date1', nds='üm un bi $date1', nl='ca. $date1', no='ca. $date1', pl={'około $date1', case={'gen',''}}, pt='cerca de $date1', qu='$date1chá', ro='circa $date1', ru={'около $date1', case={'gen',''}}, sk='cca $date1', sl={function(date1, state) return 'ok. ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='c:a $date1', -- template:Circa used 'ca $date1' uk={'близько $date1', case={'gen', ''}}, ur='قریباً $date1', vi='vào khoảng $date1', ['zh-hans']='约$date1', ['zh-hant']='約$date1', default='<span style="color:red">circa $date1</span>', }, ['uncertain'] = { nDates = 1, ['zh-hans']='$date1(?)', ['zh-hant']='$date1(?)', default='$date1 (?)', }, probably = { -- see Q56644435 nDates = 1, ast = 'probablemente $date1', bn = 'সম্ভবত $date1', ca = 'probablement $date1', ckb = 'لەوانەیە $date1', cs = 'pravděpodobně $date1', de = 'wahrscheinlich $date1', el = 'πιθανόν $date1', en = 'probably $date1', ['en-gb']='probably $date1', ['en-ca']='probably $date1', eo = 'probable $date1', es = 'probable $date1', fa = 'احتمالاً $date1', fr = 'probablement $date1', he = 'כפי הנראה $date1', hu = 'feltehetőleg $date1', it = 'probabile $date1', mk = 'веројатно $date1', ml = 'ഒരുപക്ഷേ $date1', nl = 'waarschijnlijk $date1', pl = 'prawdopodobnie $date1', pt = 'provavelmente $date1', ru = 'вероятно $date1', sv = 'antagligen $date1', uk = 'ймовірно $date1', default='<span style="color:red">probably $date1</span>', }, possibly = { -- see Q30230067 nDates = 1, ast = 'posiblemente $date1', bn = 'যথাসম্ভব $date1', ckb = 'لەوانەیە $date1', cs = 'možná $date1', de = 'möglicherweise $date1', en = 'possibly $date1', ['en-gb']='possibly $date1', ['en-ca']='possibly $date1', eo = 'eble $date1', el = 'πιθανόν $date1', fa = 'احتمالاً $date1', fr = 'peut-être $date1', hu = 'talán $date1', mk = 'можеби $date1', ml = 'മിക്കവാറും $date1', nl = 'mogelijk $date1', pl = 'być może $date1', pt = 'possivelmente $date1', ro = 'poate $date1', ru = 'возможно $date1', sv = 'möjligen $date1', uk = 'можливо $date1', default='<span style="color:red">possibly $date1</span>', }, presumably = { -- see Q18122778 nDates = 1, ast = 'presumiblemente $date1', ckb = 'دەشێت $date1', de = 'vermutlich $date1', en = 'presumably $date1', ['en-gb']='presumably $date1', ['en-ca']='presumably $date1', fr = 'présumé $date1', it = 'presumibilmente $date1', ml = 'ഊഹപ്രകാരം $date1', ru = 'предположительно $date1', pl = 'przypuszczalnie $date1', uk = 'здогадно $date1', default='<span style="color:red">presumably $date1</span>', }, ['year unknown']= { nDates = 0, ca='Any desconegut', ['be-tarask']='Год невядомы', bn='অজানা বছর', ckb='ساڵی نەزاندراو', cs='neznámý rok', de='Jahr unbekannt', el='άγνωστο έτος', en='year unknown', ['en-gb']='year unknown', ['en-ca']='year unknown', eo='jaro nesciata', es='Año desconocido', fa='سال نامعلوم', fi='tuntematon vuosi', fr='Année inconnue', he='שנה לא ידועה', hsb='lěto njeznate', hr='nepoznata godina', it='anno sconosciuto', ja='年代不明', la='Sine anno', ml='വർഷം ലഭ്യമല്ല', mk='непозната година', nb='ukjent år', nn='ukjent år', nl='zonder jaar', no='ukjent år', pl='rok nieznany', qu='Mana riqsisqa wata', ru='Год неизвестен', sk='neznámy rok', sl='neznano leto', sv='Okänt år', uk='рік невідомий', ur='سال نامعلوم', vi='không rõ năm', ['zh-hans']='年份未知', ['zh-hant']='年份未知', default='<span style="color:red">year unknown</span>', }, -- ========================================== -- === Parts of a whole ===================== -- ========================================== ['firsthalf'] = { nDates = 1, ar='النصف الأول من $date1', ['be-tarask']={'першая палова $date1', case={'gen',''}}, bn='$date1-এর প্রথমার্ধ', ca='1a meitat de $date1', ckb='نیوەی یەکەمی $date1', cs={'1. polovina $date1', case={'gen',''}}, da='1. halvdel af $date1', de='1. Hälfte $date1', el='πρώτο μισό του $date1', en='first half of $date1', ['en-gb']='first half of $date1', ['en-ca']='first half of $date1', eo='unua duono de $date1', es='primera mitad de $date1', et='$date1 esimene pool', fa='نیمهٔ نخست $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 alkupuolisko' end, case={'gen',''}}, fr='1<sup>re</sup> moitié de $date1', gl='primeira metade de $date1', he='במחצית הראשונה של $date1', hsb={'prěnja połojca $date1', case={'gen',''}}, hu='$date1 első felében', hr='prva polovina $date1', it='prima metà del $date1', ja='$date1上半期', mk='прва половина на $date1', ml='$date1 ആദ്യപകുതി', nb='første halvdel av $date1', nn='fyrste halvdelen av $date1', nds='1. Helft $date1', nl='eerste helft $date1', no='første halvdel av $date1', pl={'pierwsza połowa $date1', case={'gen',''}}, pt='primeira metade de $date1', qu='$date1 ñawpaq kuskanpi', ro='prima jumătate a lui $date1', ru={'первая половина $date1', case={'gen',''}}, sk={'1. polovica $date1', case={'gen',''}}, sl={function(date1, state) return 'prva polovica ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='första halvan av $date1', uk={'перша половина $date1', case={'gen',''}}, ur='نصف اول $date1', vi='nửa đầu năm $date1', ['zh-hans']='$date1上半期', ['zh-hant']='$date1上半期', default='<span style="color:red">first half of $date1</span>', }, ['secondhalf'] = { nDates = 1, ar='النصف الثاني من $date1', ['be-tarask']={'другая палова $date1', case={'gen',''}}, bn='$date1-এর দ্বিতীয়ার্ধ', ca='2a meitat de $date1', ckb='نیوەی دووەمی $date1', cs={'2. polovina $date1', case={'gen',''}}, da='2. halvdel af $date1', de='2. Hälfte $date1', el='δεύτερο μισό του $date1', en='second half of $date1', ['en-gb']='second half of $date1', ['en-ca']='second half of $date1', eo='dua duono de $date1', es='segunda mitad de $date1', et='$date1 teine pool', fa='نیمهٔ دوم $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 loppupuolisko' end, case={'gen',''}}, fr='2<sup>e</sup> moitié de $date1', gl='segunda metade de $date1', he='במחצית השנייה של $date1', hsb={'druha połojca $date1', case={'gen',''}}, hr='druga polovina $date1', hu='$date1 második felében', it='seconda metà del $date1', ja='$date1下半期', mk='втора половина на $date1', ml='$date1 രണ്ടാം പകുതി', nb='andre halvdel av $date1', nn='andre halvdelen av $date1', nds='2. Helft vun $date1', nl='tweede helft $date1', no='andre halvdel av $date1', pl={'druga połowa $date1', case={'gen',''}}, pt='segunda metade de $date1', qu='$date1 qhipaq kuskanpi', ro='a doua jumătate a lui $date1', ru={'вторая половина $date1', case={'gen',''}}, sk={'2. polovica $date1', case={'gen',''}}, sl={'druga polovica {{IfNum | $date0 | leta}} $date1', case={'gen',''}}, sv='andra halvan av $date1', uk={'друга половина $date1', case={'gen',''}}, ur='نصف آخر $date1', vi='nửa cuối năm $date1', ['zh-hans']='$date1下半期', ['zh-hant']='$date1下半期', default='<span style="color:red">second half of $date1</span>', }, ['1quarter']= { nDates = 1, bn='$date1-এর প্রথম ত্রৈমাসিক', ['be-tarask']={'першая чвэрць $date1', case={'gen',''}}, ckb='چارەکی یەکەمی $date1', cs={'1. čtvrtina $date1', case={'gen',''}}, de='erstes Viertel $date1', en='first quarter of $date1', ['en-gb']='first quarter of $date1', ['en-ca']='first quarter of $date1', eo='unua kvarono de $date1', es='primer cuarto del $date1', fa='ربع نخست $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 ensimmäinen neljännes' end, case={'gen',''}}, fr= function(date1, state) return 'premier quart '.. frOf[state.precision] ..' $date1' end, he='רבעון ראשון של $date1', hsb={'prěnja štwórćina $date1', case={'gen',''}}, hr='prva četvrtina $date1', it='primo quarto del $date1', ja='$date1第1四半期', ml='$date1 ഒന്നാം ത്രൈമാസം', nb='første kvartal av $date1', nl='eerste kwart $date1', nn='fyrste kvartalet av $date1', no='første kvartal av $date1', pl={'pierwsza ćwiartka $date1', case={'gen',''}}, qu='$date1 ñawpaq ñiqin tawa phatmanpi', ru={'первая четверть $date1', case={'gen',''}}, sk={'1. štvrtina $date1', case={'gen',''}}, sl={'prva četrtina {{IfNum | $date0 | leta}} $date1', case={'gen',''}}, sl={function(date1, state) return 'prva četrtina ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='första kvartalet av $date1', uk={'1-ша чверть $date1', case={'gen',''}}, ur='پہلی سہ ماہی $date1', vi='quý 1 năm $date1', ['zh-hans']='$date1第一季度', ['zh-hant']='$date1第一季度', default='<span style="color:red">first quarter of $date1</span>', }, ['2quarter']= { nDates = 1, ['be-tarask']={'другая чвэрць $date1', case={'gen',''}}, bn='$date1-এর দ্বিতীয় ত্রৈমাসিক', ckb='چارەکی دووەمی $date1', cs={'2. čtvrtina $date1', case={'gen',''}}, de='zweites Viertel $date1', en='second quarter of $date1', ['en-gb']='second quarter of $date1', ['en-ca']='second quarter of $date1', eo='dua kvarono de $date1', es='segundo cuarto del $date1', fa='ربع دوم $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 toinen neljännes' end, case={'gen',''}}, fr= function(date1, state) return 'deuxième quart '.. frOf[state.precision] ..' $date1' end, he='רבעון שני של $date1', hsb={'druha štwórćina $date1', case={'gen',''}}, hr='druga četvrtina $date1', it='secondo quarto del $date1', ja='$date1第2四半期', ml='$date1 രണ്ടാം ത്രൈമാസം', nb='andre kvartal av $date1', nl='tweede kwart $date1', nn='andre kvartalet av $date1', no='andre kvartal av $date1', pl={'druga ćwiartka $date1', case={'gen',''}}, qu='$date1 iskay ñiqin tawa phatmanpi', ru={'вторая четверть $date1', case={'gen',''}}, sk={'2. štvrtina $date1', case={'gen',''}}, sl={function(date1, state) return 'druga četrtina ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='andra kvartalet av $date1', uk={'2-га чверть $date1', case={'gen',''}}, ur='دوسری سہ ماہی $date1', vi='quý 2 năm $date1', ['zh-hans']='$date1第二季度', ['zh-hant']='$date1第二季度', default='<span style="color:red">second quarter of $date1</span>', }, ['3quarter']= { nDates = 1, ['be-tarask']={'трэцяя чвэрць $date1', case={'gen',''}}, bn='$date1-এর তৃতীয় ত্রৈমাসিক', ckb='چارەکی سێیەمی $date1', cs={'3. čtvrtina $date1', case={'gen',''}}, de='drittes Viertel $date1', en='third quarter of $date1', ['en-gb']='third quarter of $date1', ['en-ca']='third quarter of $date1', eo='tria kvarono de $date1', es='tercer cuarto del $date1', fa='ربع سوم $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 kolmas neljännes' end, case={'gen',''}}, fr= function(date1, state) return 'troisième quart '.. frOf[state.precision] ..' $date1' end, he='רבעון שלישי של $date1', hsb={'třeća štwórćina $date1', case={'gen',''}}, hr='treća četvrtina $date1', it='terzo quarto del $date1', ja='$date1第3四半期', ml='$date1 മൂന്നാം ത്രൈമാസം', nb='tredje kvartal av $date1', nl='derde kwart $date1', nn='tredje kvartalet av $date1', no='tredje kvartal av $date1', pl={'trzecia ćwiartka $date1', case={'gen',''}}, qu='$date1 kimsa ñiqin tawa phatmanpi', sk={'3. štvrtina $date1', case={'gen',''}}, sl={function(date1, state) return 'tretja četrtina ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, ru={'третья четверть $date1', case={'gen',''}}, sv='tredje kvartalet av $date1', uk={'3-тя чверть $date1', case={'gen',''}}, ur='تیسری سہ ماہی $date1', vi='quý 3 năm $date1', ['zh-hans']='$date1第三季度', ['zh-hant']='$date1第三季度', default='<span style="color:red">third quarter of $date1</span>', }, ['4quarter']= { nDates = 1, ['be-tarask']={'чацьвёртая чвэрць $date1', case={'gen',''}}, bn='$date1-এর চতুর্থ ত্রৈমাসিক', ckb='چارەکی چوارەمی $date1', cs={'4. čtvrtina $date1', case={'gen',''}}, de='letztes Viertel $date1', en='fourth quarter of $date1', ['en-gb']='fourth quarter of $date1', ['en-ca']='fourth quarter of $date1', eo='kvara kvarono de $date1', es='último cuarto del $date1', fa='ربع چهارم $date1', fi={function(date1, state) return decYear(state,'fi','gen') .. '$date1 viimeinen neljännes' end, case={'gen',''}}, fr= function(date1, state) return 'quatrième quart '.. frOf[state.precision] ..' $date1' end, he='רבעון רביעי של $date1', hsb={'poslednja štwórćina $date1', case={'gen',''}}, hr='četvrta četvrtina $date1', it='quarto quarto del $date1', ja='$date1第4四半期', ml='$date1 നാലാം ത്രൈമാസം', nb='fjerde kvartal av $date1', nl='vierde kwart $date1', nn='tredje kvartalet av $date1', no='fjerde kvartal av $date1', pl={'czwarta ćwiartka $date1', case={'gen',''}}, qu='$date1 qhipaq ñiqin tawa phatmanpi', ru={'последняя четверть $date1', case={'gen',''}}, sk={'4. štvrtina $date1', case={'gen',''}}, sl={function(date1, state) return 'zadnja četrtina ' .. decYear(state,'sl','gen') .. ' $date1' end, case={'gen',''}}, sv='fjärde kvartalet av $date1', uk={'4-та чверть $date1', case={'gen',''}}, ur='چوتھی سہ ماہی $date1', vi='quý 4 năm $date1', ['zh-hans']='$date1第四季度', ['zh-hant']='$date1第四季度', default='<span style="color:red">forth quarter of $date1</span>', }, -- ========================================== -- === Seasons ============================== -- ========================================== ['spring'] = { nDates = 1, ar='ربيع $date1', ['be-tarask']='вясна $date1', bn='বসন্তকাল $date1', ca='primavera $date1', ckb='بەھاری $date1', cs={'jaro $date1', case={'gen', ''}}, da='Forår $date1', de='Frühling $date1', en='Spring $date1', ['en-gb']='Spring $date1', ['en-ca']='Spring $date1', eo='printempo $date1', es='primavera de $date1', et='$date1 kevad', fa='بهار $date1', fi='Kevät $date1', fr='printemps $date1', gl='primavera $date1', he='אביב $date1', hr='proljeće $date1', hsb='nalěćo $date1', hu='$date1 tavasza', it='primavera $date1', ja='{{#if:$date1|$date1年}}春', mk='пролет $date1', ml='$date1 വസന്തം', nb='våren $date1', nds='Vörjohr $date1', nn='våren $date1', nl='voorjaar $date1', no='våren $date1', pl='wiosna $date1', pt='primavera de $date1', ro='primăvara lui $date1', ru='весна $date1', sk={'jar $date1', case={'gen', ''}}, sl='pomlad $date1', sv='våren $date1', uk='весна $date1', ur='بہار $date1', vi='mùa đông năm $date1', ['zh-hans']='{{#if:$date1|$date1年}}春季', ['zh-hant']='{{#if:$date1|$date1年}}春季', default='<span style="color:red">Spring $date1</span>', }, ['summer'] = { nDates = 1, ar='صيف $date1', ['be-tarask']='лета $date1', bn='গ্রীষ্মকাল $date1', ca='estiu $date1', ckb='ھاوینی $date1', cs={'léto $date1', case={'gen', ''}}, da='Sommer $date1', de='Sommer $date1', en='Summer $date1', ['en-gb']='Summer $date1', ['en-ca']='Summer $date1', eo='somero $date1', es='verano de $date1', et='$date1 suvi', fa='تابستان $date1', fi='Kesä $date1', fr='été $date1', gl='verán $date1', he='קיץ $date1', hr='ljeto $date1', hsb='lěćo $date1', hu='$date1 nyara', it='estate $date1', ja='{{#if:$date1|$date1年}}夏', mk='лето $date1', ml='$date1 വേനൽ', nb='sommeren $date1', nds='Sommer $date1', nn='sumaren $date1', nl='zomer $date1', no='sommeren $date1', pl='lato $date1', pt='verão de $date1', ro='vara lui $date1', ru='лето $date1', sk={'leto $date1', case={'gen', ''}}, sl='poletje $date1', sv='sommaren $date1', uk='літо $date1', ur='گرما $date1', vi='mùa hè năm $date1', ['zh-hans']='{{#if:$date1|$date1年}}夏季', ['zh-hant']='{{#if:$date1|$date1年}}夏季', default='<span style="color:red">Summer $date1</span>', }, ['autumn'] = { nDates = 1, ar='خريف $date1', ['be-tarask']='восень $date1', bn='শরত্কাল $date1', ca='tardor $date1', ckb='پاییزی $date1', cs={'podzim $date1', case={'gen', ''}}, da='Efterår $date1', de='Herbst $date1', en='Autumn $date1', ['en-gb']='Autumn $date1', ['en-ca']='Autumn $date1', eo='aŭtuno $date1', es='otoño de $date1', et='$date1 sügis', fa='پاییز $date1', fi='Syksy $date1', fr='automne $date1', gl='outono $date1', he='סתיו $date1', hr='jesen $date1', hsb='nazyma $date1', hu='$date1 ősze', it='autunno $date1', ja='{{#if:$date1|$date1年}}秋', mk='есен $date1', ml='$date1 ശരത്കാലം', nb='høsten $date1', nds='Harvst $date1', nn='hausten $date1', nl='herfst $date1', no='høsten $date1', pl='jesień $date1', pt='outono de $date1', ro='toamna lui $date1', ru='осень $date1', sk={'jeseň $date1', case={'gen', ''}}, sl='jesen $date1', sv='hösten $date1', uk='осінь $date1', ur='خزاں $date1', vi='mùa thu năm $date1', ['zh-hans']='{{#if:$date1|$date1年}}秋季', ['zh-hant']='{{#if:$date1|$date1年}}秋季', default='<span style="color:red">Autumn $date1</span>', }, ['winter'] = { nDates = 1, ar='شتاء $date1', ['be-tarask']='зіма $date1', bn='শীতকাল $date1', ca='hivern $date1', ckb='زستانی $date1', cs={'zima $date1', case={'gen', ''}}, da='Vinter $date1', de='Winter $date1', en='Winter $date1', ['en-gb']='Winter $date1', ['en-ca']='Winter $date1', eo='vintro $date1', es='invierno de $date1', et='$date1 talv', fa='زمستان $date1', fi='Talvi $date1', fr='hiver $date1', gl='inverno $date1', he='חורף $date1', hr='zima $date1', hsb='zyma $date1', hu='$date1 tele', it='inverno $date1', ja='{{#if:$date1|$date1年}}冬', mk='зима $date1', ml='$date1 ശൈത്യകാലം', nds='Winter $date1', nl='winter $date1', nb='vinteren $date1', nn='vinteren $date1', no='vinteren $date1', pl='zima $date1', pt='inverno de $date1', ro='iarna lui $date1', ru='зима $date1', sk={'zima $date1', case={'gen', ''}}, sl='zima $date1', sv='vintern $date1', uk='зима $date1', ur='سرما $date1', vi='mùa đông năm $date1', ['zh-hans']='{{#if:$date1|$date1年}}冬季', ['zh-hant']='{{#if:$date1|$date1年}}冬季', default='<span style="color:red">Winter $date1</span>', }, -- ========================================== -- === Decade/Century/Millennium ============ -- ========================================== ['decade'] = { nDates = 1, ar='أعوام $date1', ['be-tarask']='$date1-я гады', bn='$date1-এর দশক', ca='dècada del $date1', ckb='دەیەی $date1', cs=function(date1, state) return ((date1 % 100 == 0) and 'první dekáda' or ((date1 % 100) .. '. léta')) .. ' ' .. (math.floor(date1 / 100) + 1) .. '. století' end, da="$date1'erne", de='$date1er-Jahre', el='δεκαετία του $date1', en='$date1s', ['en-gb']='$date1s', ['en-ca']='$date1s', eo='$date1-aj jaroj', es='años $date1', et='$date1ndad', fa='دههٔ $date1', fi='$date1-luvulla', fr='années $date1', gl='anos $date1', he='שנות ה-$date1', hr='$date1-ih', hsb='$date1te lěta', hu='$date1-s évek', it="{{#ifexpr: $date1 mod 100 = 0 | primo decennio del {{Roman|{{#expr: ($date1 / 100) + 1}}}} sec. | anni '{{#expr: $date1 mod 100 }} del {{Roman|{{#expr: floor($date1 / 100) + 1}}}} sec.}}", ja='$date1年代', mk='$date1-ти', ml='ദശകം $date1-ൽ', nb='$date1-årene', nn='$date1-åra', nds='Johren $date1', nl='jaren $date1', no='$date1-årene', pl=function(date1, state) local str = 'lata '..(date1 % 100)..'. ' if (date1 % 100 == 0) then str = 'pierwsza dekada ' elseif (date1 % 100 == 10) then str = 'druga dekada ' end return str .. Roman(math.floor(date1 / 100) + 1) .. ' wieku' end, pt='década de $date1', qu='$date1 watakuna', ro='anii $date1', ru='$date1-е годы', sk=function(date1, state) return ((date1 % 100 == 0) and 'prvé desaťročie' or ((date1 % 100) .. '. roky')) .. ' ' .. (math.floor(date1 / 100) + 1) .. '. storočia' end, sl='$date1-ta leta', sv='$date1-talet{{#switch:$date1|1300|1400|1500|1600|1700|1800|1900|2000|2100=&nbsp;(decennium)}}', uk='$date1-ті роки', ur='دہائی $date1', vi='thập niên $date1', ['zh-hans']='$date1年代', ['zh-hant']='$date1年代', default='<span style="color:red">$date1s</span>', }, ['century'] = { nDates = 1, ar='القرن $date1', ['be-tarask']=function(date1, state) return Ordinal(date1, 'be-tarask') .. ' стагодзьдзе' end, bn=function(date1, state) return Ordinal(date1, 'bn') .. ' শতাব্দী' end, ca='segle {{small-caps|{{lc:{{Roman year|$date1}}}}}}', ckb='سەدەی $date1', cs='$date1. století', da='$date1. århundrede', de='$date1. Jahrhundert', el='$date1ος αιώνας', en=function(date1, state) return Ordinal(date1, 'en') .. ' century' end, ['en-gb']=function(date1, state) return Ordinal(date1, 'en-gb') .. ' century' end, ['en-ca']=function(date1, state) return Ordinal(date1, 'en-ca') .. ' century' end, eo='$date1-a jarcento', es=function(date1, state) return 'siglo ' .. Roman(date1) end, et='$date1. sajand', fa='سدهٔ $date1', fi='{{#expr: ($date1 - 1) * 100}}-luvulla', fr=function(date1, state) return Ordinal(date1, 'fr', 'roman', 'm') .. ' siècle' end, gl=function(date1, state) return 'século ' .. Roman(date1) end, he='המאה ה-$date1', hr='$date1. stoljeće', hsb='$date1. lětstotk', hu='$date1. évszázad', it=function(date1, state) return Roman(date1) .. ' sec.' end, ja='$date1世紀', la='saeculum $date1', mk=function(date1, state) return Roman(date1) .. ' век' end, ml='$date1-ആം നൂറ്റാണ്ട്', nb=function(date1, state) return Ordinal(date1, 'nb') .. ' århundre' end, nn=function(date1, state) return 'det ' .. Ordinal(date1, 'no') .. ' århundre' end, nds='$date1. Johrhunnert', nl='$date1e eeuw', no=function(date1, state) return Ordinal(date1, 'no') .. ' århundre' end, pl=function(date1, state) return Roman(date1) .. ' ' .. decCentury('pl',state.case) end, pt=function(date1, state) return 'século ' .. Roman(date1) end, qu='$date1 ñiqin pachakwata', ro='secolul al $date1-lea', ru=function(date1, state) return Roman(date1) .. ' век' end, sk='$date1. storočie', sl=function(date1, state) return Ordinal(date1, 'sl') .. '. stoletje' end, sv='{{#expr:$date1 - 1}}00-talet', tt=function(date1, state) return Roman(date1) .. ' гасыр' end, uk=function(date1, state) return Roman(date1) .. ' ст.' end, ur='صدی $date1', vi='thế kỷ $date1', ['zh-hans']='$date1世纪', ['zh-hant']='$date1世紀', default='<span style="color:red">$date1 century</span>', }, ['millennium'] = { nDates = 1, ['be-tarask']=function(date1, state) return Ordinal(date1, 'be-tarask') .. ' тысячагодзьдзе' end, bn=function(date1, state) return Ordinal(date1, 'bn') .. ' সহস্রাব্দ' end, ckb=function(date1, state) return 'ھەزارەی ' .. Ordinal(date1, 'ckb') end, cs='$date1. tisíciletí', de='$date1. Jahrtausend', en=function(date1, state) return Ordinal(date1, 'en') .. ' millennium' end, ['en-gb']=function(date1, state) return Ordinal(date1, 'en-gb') .. ' millennium' end, ['en-ca']=function(date1, state) return Ordinal(date1, 'en-ca') .. ' millennium' end, eo='$date1-a jarmilo', es=function(date1, state) return Roman(date1) .. ' milenio' end, fa=function(date1, state) return 'هزارهٔ ' .. Ordinal(date1, 'fa') end, fr=function(date1, state) return Ordinal(date1, 'fr', 'roman', 'm') .. ' millénaire' end, hr='$date1. tisućljeće', hsb='$date1. lěttysac', it=function(date1, state) return Roman(date1) .. ' millennio' end, ja='$date1千年紀', la='millennium $date1', ml='$date1-മത്തെ സഹസ്രാബ്ദം', nb=function(date1, state) return Ordinal(date1, 'nb') .. ' årtusen' end, nn=function(date1, state) return Ordinal(date1, 'no') .. ' tusenår' end, nl='$date1e millennium', no=function(date1, state) return Ordinal(date1, 'no') .. ' årtusen' end, pl=function(date1, state) return Roman(date1) .. ' ' .. decMillennium('pl',state.case) end, pt=function(date1, state) return Roman(date1) .. ' milénio' end, qu='$date1 ñiqin waranqawata', ru=function(date1, state) return Roman(date1) .. ' тысячелетие' end, sk='$date1. tisícročie', sl=function(date1, state) return Ordinal(date1, 'sl') .. '. tisočletje' end, sv=function(date1, state) return Ordinal(date1, 'sv') .. ' årtusendet' end, uk=function(date1, state) return Roman(date1) .. ' тис.' end, ur=function(date1, state) return 'ہزارہ ' .. Ordinal(date1, 'ur') end, vi='thiên niên kỷ $date1', ['zh-hans']=function(date1, state) return Ordinal(date1, 'zh-hans') .. '千年' end, ['zh-hant']=function(date1, state) return Ordinal(date1, 'zh-hant') .. '千年' end, --default='{{Please translate|$date1 millennium|Module:I18n/complex date}}', default='<span style="color:red">$date1 millennium</span>', }, ['million'] = { ckb = '$date1 میلیۆن ساڵ', de = '$date1 Millionen Jahre', en = '$date1 million years', ['en-gb'] = '$date1 million years', ['en-ca'] = '$date1 million years', eo = '$date1 milionoj da jaroj', es = '$date1 millones de años', nl = '$date1e miljoen jaar', fr = '$date1 millions d’années', it = '$date1 milioni di anni', ml = function(date1, state) if date1 >= 10 then return (date1 / 10) .. ' കോടി വർഷങ്ങൾ' -- 100 lakh = 1 crore else return (date1 * 10) .. ' ലക്ഷം വർഷങ്ങൾ' end end, nb = '$date1 millioner år', nl = '$date1 miljoen jaar', nn = '$date1 millionar år', pl = '$date1 milionów lat', pt = '$date1 milhões de anos', ru = '$date1 миллиона лет', uk = '$date1 мільйони років', ur = '$date1 ملین سال', ['zh-hans'] = '$date1百万年', ['zh-hant'] = '$date1百万年', default='<span style="color:red">$date1 million years</span>', nDates = 1, }, ['billion'] = { ckb = '$date1 بیلیۆن ساڵ', de = '$date1 Milliarden Jahre', en = '$date1 billion years', ['en-gb'] = '$date1 billion years', ['en-ca'] = '$date1 billion years', es = '$date1 mil millones de años', nl = '$date1e miljard jaar', fr = '$date1 milliard d’années', it = '$date1 miliardi di anni', ml = function(date1, state) return (date1 * 10) .. ' കോടി വർഷങ്ങൾ' end, nb = '$date1 milliarder år', nl = '$date1 miljard jaar', nn = '$date1 milliardar år', pl = '$date1 miliardów lat', pt = '$date1 mil milhões de anos', ru = '$date1 миллиардов лет', uk = '$date1 мільярдів років', ur = '$date1 بلین سال', ['zh-hans'] = '$date1十亿年', ['zh-hant'] = '$date1十億年', default='<span style="color:red$date1 billion years</span>', nDates = 1, }, -- ========================================== -- === Complex phrases ====================== -- ========================================== -- Phrases which for some languages can be build out of simpler phrases -- Their "default" state, used when no translation in the requested language was found, returns a "state" like -- table which is used to call the function again and built the phrase out of simple phrases -- $date1 - original date - 1 -- $date2 - original date -- if era is BC than $date1 and $date2 are swapped ['turn of the century'] = { nDates = 2, ['be-tarask']=function(date1, date2, state) return 'канец ' .. Roman(date1) ..' — пачатак ' .. Roman(date2) ..' стагодзьдзя' end, bn=function(date1, date2, state) return '$date1/' .. Ordinal(date2, 'bn') .. ' শতাব্দীর পালাবদলে' end, ckb=function(date1, date2, state) return 'سەردەمی $date1/' .. Ordinal(date2, 'ckb') .. ' سەدە' end, cs='přelom $date1. a $date2. století', de=function(date1, date2, state) return 'Jahrhundertwende $date1/' .. Ordinal(date2, 'de') end, en=function(date1, date2, state) return 'turn of the $date1/' .. Ordinal(date2, 'en') .. ' century' end, ['en-gb']=function(date1, date2, state) return 'turn of the $date1/' .. Ordinal(date2, 'en-gb') .. ' century' end, ['en-ca']=function(date1, date2, state) return 'turn of the $date1/' .. Ordinal(date2, 'en-ca') .. ' century' end, eo='ŝanĝo de la $date1-a al la $date2-a jarcentoj', es=function(date1, date2, state) return 'hacia finales del siglo ' .. Roman(date1) .. ' y principios del siglo ' .. Roman(date2) .. '' end, fi='{{#expr: $date1 * 100-100}}-luvun loppu ja {{#expr: $date2 * 100-100}}-luvun alku', fr=function(date1, date2, state) return 'fin du ' .. Ordinal(date1, 'fr', 'roman', 'm') .. ' siècle / début du ' .. Ordinal(date2, 'fr', 'roman', 'm') end, hr='kraj $date1. i početak $date2. stoljeća', hsb=function(date1, date2, state) return 'přechod ' .. Roman(date1) ..' do ' .. Roman(date2) ..' lětstotka' end, it=function(date1, date2, state) return 'fine del ' .. Roman(date1) .. ' sec. e inizio del ' .. Roman(date2) .. ' sec.' end, ja='$date1世紀から$date2世紀への変わり目', ml='$date1 നൂറ്റാണ്ട് അവസാനവും $date2 നൂറ്റാണ്ട് ആദ്യവും', nb=function(date1, date2, state) return 'slutten av det ' .. Ordinal(date1, 'nb') .. ' århundre/begynnelsen av det ' .. Ordinal(date2, 'nb') .. ' århundre' end, nl=function(date1, date2, state) return 'eind $date1e/begin $date2e eeuw' end, nn=function(date1, date2, state) return 'slutten av det ' .. Ordinal(date1, 'nn') .. ' hundreåret/byrjinga av det ' .. Ordinal(date2, 'nn') .. ' hundreåret' end, pl=function(date1, date2, state) return 'przełom ' .. Roman(date1) ..' i ' .. Roman(date2) ..' wieku' end, qu="$date1 ñiqin waranqawatamanta $date2 ñiqin waranqawataman t'ikrasqanpi", ru=function(date1, date2, state) return 'конец ' .. Roman(date1) ..' — начало ' .. Roman(date2) ..' века' end, sk='prelom $date1. a $date2. storočia', sl=function(date1, date2, state) return 'pozno ' .. Ordinal(date1, 'sl') .. '. in zgodnje ' .. Ordinal(date2, 'sl') .. '. stoletje' end, sv=function(date1, date2, state) return '$date1/' .. Ordinal(date2, 'sv') .. ' årtusendets sekelskifte' end, uk=function(date1, date2, state) return 'кін. ' .. Roman(date1) ..' ст. — поч. ' .. Roman(date2) ..' ст.' end, vi='bước sang thế kỷ $date2', ['zh-hans']=function(date1, date2, state) return '$date1世纪末' .. Ordinal(date2, 'zh-hans') .. '世纪初' end, ['zh-hant']=function(date1, date2, state) return '$date1世紀末' .. Ordinal(date2, 'zh-hant') .. '世紀初' end, default=function(date1, date2, state) return {conj='and', adj1 = 'late', unit1='century', era1=state.era1, adj2 = 'early', unit2='century', era2=state.era2} end, }, -- $date1 - original date - 10 -- $date2 - original date -- if era is BC than $date1 and $date2 are swapped ['turn of the decade']={ nDates = 2, ['be-tarask']='канец $date1-х — пачатак $date2-х гг.', bn='$date1-এর দশকের শেষে এবং $date2-এর দশকের শুরুতে', cs=function(date1, date2, state) local decade1 = math.floor((date1 % 100) / 10) local decade2 = math.floor((date2 % 100) / 10) local century1 = math.floor( date1 / 100) + 1 local century2 = math.floor( date2 / 100) + 1 if decade1 == 0 and date1 < date2 then return "přelom 1. a 2. dekády " .. century1 .. ". století" elseif decade2 == 0 and date2 < date1 then return "přelom 9. a 10. dekády " .. century1 .. ". století" elseif (decade1 == 9 and date1 < date2) or (decade2 == 9 and date2 < date1) then return "přelom " .. century1 .. ". a " .. century2 .. ". století" else return "přelom " .. decade1 .. "0. a " .. decade2 .. "0. let " .. century1 .. ". století" end end, de='späte $date1er und frühe $date2er', ckb='کۆتایییەکانی $date1 و سەرەتاکانی $date2', en='late $date1s and early $date2s', ['en-gb']='late $date1s and early $date2s', ['en-ca']='late $date1s and early $date2s', eo='ŝanĝo de la $date1-a al la $date2-a jardekoj', es='finales de los años $date1 y principios de los años $date2', fa='اواخر دههٔ $date1 و اوایل دههٔ $date2', fi='$date1-luvun loppu ja $date2-luvun alku', fr='fin des années $date1 / début des années $date2', hr='kraj $date1-ih i početak $date2-ih', hsb='pózdnje $date1te a zažne $date2te lěta', it='fine degli anni $date1 / inizio degli anni $date2', ja='$date1年代末から$date2年代初頭', ml='$date1കൾ അവസാനവും $date2കൾ ആദ്യവും', nb = 'sent på $date1-tallet og tidlig på $date2-tallet', nl = 'eind $date1e/begin $date2e decennium', nn = 'seint på $date1-talet og tidleg på $date2-talet', pl=function(date1, date2, state) local decade1 = math.floor(date1 % 100) local decade2 = math.floor(date2 % 100) local century1 = Roman(math.floor( date1 / 100) + 1) local century2 = Roman(math.floor( date2 / 100) + 1) if decade1 == 0 and date1 < date2 then return "przełom pierwszej i drugiej dekady " .. century1 .. " wieku" elseif decade1 == 10 and date1 < date2 then return "przełom drugiej dekady i lat 20. " .. century1 .. " wieku" elseif decade1 == 90 and decade2 == 0 then return "przełom ostatniej dekady " .. century1 .. " i pierwszej dekady " .. century2 .. " wieku" else return "przełom lat " .. decade1 .. ". i " .. decade2 .. ". " .. century1 .. " wieku" end end, qu='qhipaq $date1 watakuna ñawpaq $date2 watakunapas', ru='конец $date1-х — начало $date2-х гг.', sv='sena $date1-talet och tidiga $date2-talet', sk=function(date1, date2, state) local decade1 = math.floor((date1 % 100) / 10) local decade2 = math.floor((date2 % 100) / 10) local century1 = math.floor( date1 / 100) + 1 local century2 = math.floor( date2 / 100) + 1 if decade1 == 0 and date1 < date2 then return "prelom 1. a 2. desaťročia " .. century1 .. ". storočia" elseif decade2 == 0 and date2 < date1 then return "prelom 9. a 10. desaťročia " .. century1 .. ". storočia" elseif (decade1 == 9 and date1 < date2) or (decade2 == 9 and date2 < date1) then return "prelom " .. century1 .. ". a " .. century2 .. ". storočia" else return "prelom " .. decade1 .. "0. a " .. decade2 .. "0. rokov " .. century1 .. ". storočia" end end, sl='pozna $date1-ta in zgodnja $date2-ta leta', uk='кінець $date1-х — початок $date2-х рр.', ur='اخیر دہائی $date1 و ابتدا دہائی $date2', vi='bước sang thập niên $date2', ['zh-hans']='$date1年代末$date2年代初', ['zh-hant']='$date1年代末$date2年代初', default=function(date1, date2, state) return {conj='and', adj1 = 'late', unit1='decade', era1=state.era1, adj2 = 'early', unit2='decade', era2=state.era2} end, }, -- $date1 - original date - 1 -- $date2 - original date -- if era is BC than $date1 and $date2 are swapped ['turn of the year']={ nDates = 2, ['be-tarask']='канец $date1 — пачатак $date2 г.', bn='$date1-এর শেষে এবং $date2-এর শুরুতে', ckb='کۆتاییی $date1 و سەرەتای $date2', cs='přelom roku $date1/$date2', de='Jahreswechsel $date1/$date2', en='late $date1 and early $date2', ['en-gb']='late $date1 and early $date2', ['en-ca']='late $date1 and early $date2', eo='jarŝanĝo $date1 / $date2', es='finales de $date1 y principios de $date2', fa='اواخر $date1 و اوایل $date2', fi='vuoden $date1 loppu ja vuoden $date2 alku', fr='fin $date1 / début $date2', hsb='kónc $date1/spočatk $date2', ja='$date1年末から$date2年初め', ml='$date1 അവസാനവും $date2 ആദ്യവും', nb='årsskiftet $date1/$date2', nl='eind $date1 en begin $date2', nn='årsskiftet $date1/$date2', pl='koniec $date1 i początek $date2 roku', qu="$date1 qhipaq p'unchawkuna $date2 ñawpaq p'unchawkunapas", ru='конец $date1 — начало $date2 г.', sk='prelom rokov $date1/$date2', sl='konec leta $date1 in začetek $date2', sv='sena $date1 och tidiga $date2', uk='кінець $date1 — початок $date2 р.', ur='اخیر $date1 و ابتدا $date2', vi='bước sang năm $date2', ['zh-hans']='$date1年末$date2年初', ['zh-hant']='$date1年末$date2年初', default=function(date1, date2, state) return {conj='and', adj1 = 'late', unit1='year', era1=state.era1, adj2 = 'early', unit2='year', era2=state.era2} end, }, ['circa2'] = { nDates = 2, ar='حوالي $date1 – $date2', ['be-tarask']={'прыкладна з $date1 да прыкладна $date2', case={'gen','gen'}}, bn='প্রায় $date1 এবং প্রায় $date2-এর মাঝামাঝি', ca='cap a $date1 o $date2', ckb='بە نزیکی لەنێوان $date1 و $date2', cs='cca $date1 až $date2', da='ca. mellem $date1 og $date2', de='etwa $date1 bis $date2', el='περίπου μεταξύ $date1 και $date2', en='between c. $date1 and c. $date2', ['en-gb']='between circa $date1 and circa $date2', ['en-ca']='between circa $date1 and circa $date2', eo='proksimume de $date1 ĝis $date2', es='hacia $date1 o $date2', et='ca. $date1 ja $date2 vahel', fa='حدوداً بین $date1 و $date2', fi={function(date1, date2, state) return 'suunnilleen ' .. decYear(state,'fi','xxx') .. '$date1 ja $date2 välisenä aikana' end, case={'gen','gen'}}, fr='vers $date1-$date2', gl='entre $date1 e $date2', he='בסביבות ה- $date1 עד $date2', hr='oko između $date1 i $date2', hsb={'wot něhdźe $date1 do něhdźe $date2', case={'gen','gen'}}, hu='$date1 és $date2 között', it='tra il $date1 e il $date2 circa', ja='$date1頃と$date2頃の間', mk='~ помеѓу $date1 и $date2', ml='ഉദ്ദേശം $date1-നും ഉദ്ദേശം $date2-നും ഇടയ്ക്ക്', nb='ca. mellom $date1 og $date2', nn='ca. mellom $date1 og $date2', nds='üm un bi $date1 bet $date2', nl='ca. $date1 tot $date2', no='ca. mellom $date1 og $date2', pl={'od około $date1 do około $date2', case={'gen','gen'}}, pt='cerca de $date1 a $date2', qu="$date1 p'unchawmantachá $date2 p'unchawkamachá", ro='circa $date1 – $date2', ru={'с около $date1 до, примерно, $date2', case={'gen','gen'}}, sk='cca $date1 až $date2', sl={function(date1, date2, state) return 'ok. ' .. decYear(state,'sl','gen') .. ' $date1 do $date2' end, case={'gen','gen'}}, sv='c:a $date1 till $date2', uk={'з близько $date1 до, приблизно, $date2', case={'gen','gen'}}, ur='قریباً بین $date1 و $date2', vi='giữa vào khoảng $date1 và vào khoảng $date2', ['zh-hans']='约$date1~$date2间', ['zh-hant']='約$date1~$date2間', default=function(date1, date2, state) return {conj='between', adj1 = 'circa', unit1=state.unit1, era1=state.era1, adj2 = 'circa', unit2=state.unit2, era2=state.era2} end, }, -- ========================================== -- === Eras and calendars =================== -- ========================================== ['julian'] = { nDates = 2, ar='$date1 ($date2 حسب [[w:ar:التقويم اليولياني|تقويم يولياني]])', ['be-tarask']='$date1 ($date2 паводле [[w:be-tarask:Юліянскі каляндар|ст. ст.]]', bn='$date1 (জুলিয়ান ক্যালেন্ডারে $date2)', ca='$date1 ($date2 segons el [[w:ca:Calendari julià|calendari julià]])', ckb='$date1 ($date2 بەپێی [[w:ڕۆژژمێری جولیانی|ڕۆژژمێری جولیانی]])', cs='$date1 ($date2 podle [[w:cs:Juliánský kalendář|juliánského kalendáře]])', da='$date1 ($date2 [[w:da:Julianske kalender|Julianske kalender]])', de='$date1 ($date2 [[w:de:Julianischer Kalender|Julianischer Kalender]])', el='$date1 ($date2 με το [[w:el:Ιουλιανό ημερολόγιο|Ιουλιανό ημερολόγιο]])', en='$date1 ($date2 in [[w:Old Style and New Style dates|Julian calendar]])', ['en-gb']='$date1 ($date2 in [[w:Old Style and New Style dates|Julian calendar]])', ['en-ca']='$date1 ($date2 in [[w:Old Style and New Style dates|Julian calendar]])', fa='$date1 ($date2 در [[w:fa:Old Style and New Style dates|تقویم جولیان]])', eo='$date1 ($date2 laŭ la [[w:eo:Julia kalendaro|Julia Kalendaro]])', es='$date1 ($date2 según el [[w:es:Calendario juliano|calendario juliano]])', et='$date1 ($date2 [[w:et:Juliuse kalender|Juliuse kalendris]])', fi='$date1 ($date2 [[w:fi:juliaaninen kalenteri|juliaanisen kalenterin]] mukaan)', fr='$date1 ($date2 dans le [[w:fr:calendrier julien|calendrier julien]])', gl='$date1 ($date2 segundo o [[w:gl:Calendario xuliano|calendario xuliano]])', hu='$date1 ($date2 a [[w:hu:Julianus-naptár|Julián naptárban]])', hr='$date1 ($date2 prema [[w:hr:Julijanski kalendar|julijanskom kalendaru]])', he='$date1 ($date2 ב[[w:he:לוח היוליאני|הלוח היוליאני]])', hsb='$date1 ($date2 po julianiskej protyce)', it='$date1 ($date2 secondo il [[w:it:Calendario giuliano|calendario giuliano]])', ja='$date1([[w:ja:ユリウス暦|ユリウス暦]] $date2)', mk='$date1 ($date2 по [[w:mk:Јулијански календар|Јулијанскиот календар]])', ml='$date1 ([[w:ml:ജൂലിയൻ കാലഗണനാരീതി|ജൂലിയൻ വർഷത്തിൽ]] $date2)', ml='$date1 ([[w:Old Style and New Style dates|ജൂലിയൻ കാലഗണനാരീതിയിൽ]] $date2)', nds='$date1 ($date2 na’n [[w:nds:Juliaansch Klenner|Juliaanschen Klenner]])', nb='$date1 ($date2 etter [[w:no:Juliansk kalender|den julianske kalenderen]])', nn='$date1 ($date2 etter [[w:no:Juliansk kalender|den julianske kalenderen]])', nl='$date1 ($date2 volgens de [[w:nl:Juliaanse kalender|Juliaanse kalender]])', no='$date1 ($date2 etter [[w:no:Juliansk kalender|den julianske kalender]])', pl='$date1 ($date2 wedle [[w:pl:Daty nowego i starego porządku|kalendarza juliańskiego]])', pt='$date1 ($date2 no [[w:pt:Mudança para o calendário gregoriano|calendário juliano]])', qu='$date1 ($date2 [[w:qu:Hulyanu kalindaryu|Hulyanu kalindaryupi]])', ro='$date1 ($date2 în [[w:ro:calendarul iulian|calendarul iulian]])', ru='$date1 ($date2 [[w:ru:Юлианский календарь|по ст. ст.]])', sk='$date1 ($date2 podľa [[w:sk:Juliánsky kalendár|juliánskeho kalendára]])', sl='$date1 ($date2 po [[w:sl:Julijanski koledar|julijanskem koledarju]])', sv='$date1 ($date2 [[w:sv:Gamla stilen|gamla stilen]])', uk='$date1 ($date2 [[w:uk:Юліанський календар|Юліанський календар]])', ur='$date1 ($date2 حسب [[w:ur:نئے اور پانے انداز کی تاریخ|جولین تقویم]])', vi='$date1 ($date2 trong [[w:vi:Lịch Julius|lịch Juliêng]])', ['zh-hans']='$date1([[w:Old Style and New Style dates|格里历]]$date2)', ['zh-hant']='$date1([[w:Old Style and New Style dates|格里曆]]$date2)', default='<span style="color:red">$date1 ($date2 in [[w:Old Style and New Style dates|Julian calendar]])</span>', }, ['ad'] = { nDates = 1, ['be-tarask']='$date1 [[w:be-tarask:ад Нараджэньня Хрыста|пасьля Н. Х.]]', bn ='$date1 খ্রিস্টাব্দ', ca ='$date1 dC', ckb ='$date1 [[w:ckb:ئانۆ دۆمینی|پاز]]', cs ='$date1 n. l.', de ='$date1 n. Chr.', el ='$date1 μ.Χ.', en ='$date1 [[w:en:Anno Domini|AD]]', ['en-gb'] ='$date1 [[w:en:Anno Domini|AD]]', ['en-ca'] ='$date1 [[w:en:Anno Domini|AD]]', eo ='$date1 pK', es ='$date1 [[w:es:Anno Dómini|d. C.]]', fa ='$date1 م.', fi ='$date1 jaa.', fr ='$date1 ap. J.-C.', gl ='$date1 d.C.', he ='$date1 לספירה', hu ='i. sz. $date1', hr ='$date1 p. Kr.', hsb='$date1 po Chr.', it ='$date1 d.C.', ja ='西暦$date1', mk ='$date1 н.е.', ml ='ക്രി.വ. $date1', nb ='$date1 e.Kr.', nl ='$date1 n.Chr.', nn ='$date1 e.Kr.', no ='$date1 e.Kr.', pl ='$date1 [[w:pl:naszej ery|n.e.]]', ro ='$date1 d.Hr.', ru ='$date1 н. э.', sk ='$date1 po Kr.', sl ='$date1 n. št.', sv ='$date1 [[w:sv:Efter Kristus|e.Kr.]]', uk ='$date1 н. е.', ur ='$date1 ء', vi ='$date1 [[w:vi:Công Nguyên|CN]]', ['zh-hans'] ='[[w:zh:基督纪年|公元]]$date1', ['zh-hant'] ='[[w:zh:基督纪年|公元]]$date1', default='<span style="color:red">$date1 [[w:en:Anno Domini|AD]]</span>', }, ['ah'] = { nDates = 1, ['be-tarask']='$date1 [[w:be-tarask:Ісламскі каляндар|хіджры]]', bn ='$date1 [[w:bn:ইসলামি বর্ষপঞ্জি|হিজরী]]', ckb ='$date1 [[w:ckb:ساڵی کۆچی|سک]]', cs ='$date1 [[w:cs:Anno Hegirae|AH]]', en ='$date1 [[w:en:Hijri year|AH]]', ['en-gb'] ='$date1 [[w:en:Hijri year|AH]]', ['en-ca'] ='$date1 [[w:en:Hijri year|AH]]', eo ='$date1 post la [[w:eo:Heĝiro|Heĝiro]]', fa ='$date1 ه.ق.', fr ='$date1 [[w:fr:Calendrier musulman|A.H.]]', he ="$date1 [[w:he:הלוח המוסלמי|להג'רה]]", hr ='$date1 god. hidžre', ja ='[[w:ja:ヒジュラ暦|ヒジュラ暦]]$date1', ml ='$date1 [[w:ml:ഇസ്‌ലാമിക കലണ്ടർ|ഹിജ്റ]]', nb ='$date1 [[w:no:Den muslimske kalenderen|A.H.]]', nn ='$date1 [[w:nn:Den muslimske kalenderen|A.H.]]', pl ='$date1 [[w:pl:Kalendarz muzułmański|A.H.]]', ru ='$date1 [[w:ru:Исламский календарь|хиджры]]', sk ='$date1 [[w:sk:A. H.|A. H.]]', sv ='$date1 [[w:sv:Muslimska kalendern|A.H.]]', uk ='$date1 [[w:uk:Мусульманський календар|хіджри]]', ur ='$date1 ھ', vi ='$date1 [[w:vi:Lịch Hồi giáo|AH]]', ['zh-hans'] ='[[w:en:Hijri year|Hijri历]]$date1', ['zh-hant'] ='[[w:en:Hijri year|Hijri曆]]$date1', default='<span style="color:red">$date1 [[w:en:Hijri year|AH]]</span>', }, ['bc'] = { nDates = 1, ar ='$date1 ق.م.', ['be-tarask']='$date1 [[w:be-tarask:да Нараджэньня Хрыста|да Н. Х.]]', bn ='খ্রিস্টপূর্ব $date1', ca ='$date1 [[w:ca:Abans de Crist|aC]]', ckb ='$date1 [[w:ckb:پێش زایین|پز]]', cs ='$date1 [[w:cs:Př. n. l.|př. n. l.]]', de ='$date1 [[w:de:v. Chr.|v. Chr.]]', el ='$date1 π.Χ.', en ='$date1 [[w:en:Ante Christum Natum|BC]]', ['en-gb'] ='$date1 [[w:en:Ante Christum Natum|BC]]', ['en-ca'] ='$date1 [[w:en:Ante Christum Natum|BC]]', eo ='$date1 aK', es ='$date1 [[w:es:Antes de Cristo|a. C.]]', fa ='$date1 پ.م.', fi ='$date1 eaa.', fr ='$date1 av. J.-C.', gl ='$date1 [[w:gl:Antes de Cristo|a.C.]]', he ='$date1 לפני הספירה', hr ='$date1 [[w:hr:Pr. Kr.|pr. Kr.]]', hsb='$date1 do Chr.', hu ='i. e. $date1', it ='$date1 [[w:it:Avanti Cristo|a.C.]]', ja ='[[w:ja:紀元前|紀元前]]$date1', mk ='$date1 п.н.е.', ml ='ക്രി.മു. $date1', nds='$date1 vör uns Tied', nb ='$date1 f.Kr.', nl ='$date1 v.Chr.', nn ='$date1 f.Kr.', no ='$date1 f.Kr.', pl ='$date1 [[w:pl:przed naszą erą|p.n.e.]]', ro ='$date1 î.Hr.', ru ='$date1 до н. э.', sk ='$date1 [[w:sk:Pred Kristom|pred Kr.]]', sl ='$date1 pr. n. št.', sv ='$date1 [[w:sv:Före Kristus|f.Kr.]]', uk ='$date1 до н. е.', ur ='$date1 ق م', vi ='$date1 [[w:vi:Trước Công Nguyên|TCN]]', ['zh-hans'] ='[[w:zh:基督纪年|公元前]]$date1', ['zh-hant'] ='[[w:zh:基督纪年|公元前]]$date1', default='<span style="color:red">$date1 [[w:en:Ante Christum Natum|BC]]</span>', }, ['bp'] = { nDates = 1, ar ='$date1 [[w:ar:قبل الحاضر|قبل الحاضر]]', be ='$date1 [[w:be-tarask:Да нашых дзён|Да нашых дзён]]', ['be-tarask']='$date1 [[w:be-tarask:Да нашых дзён|Да нашых дзён]]', bn ='বর্তমানের আগে $date1', ca ='$date1 [[w:ca:BP (unitat de temps)|BP]]', ckb ='$date1 [[w:ckb:پێش ئێستا|پێش ئێستا]]', cs ='$date1 [[w:cs:B. P.|B. P.]]', de ='$date1 [[w:de:Before Present|BP]]', el ='$date1 [[w:en:Before Present|BP]]', en ='$date1 [[w:en:Before Present|BP]]', ['en-gb'] ='$date1 [[w:en:Before Present|BP]]', ['en-ca'] ='$date1 [[w:en:Before Present|BP]]', eo='$date1 [[w:eo:Before Present|BP]]', es ='$date1 [[w:es:Before Present|BP]]', et ='$date1 [[w:et:Before Present|BP]]', fa ='$date1 پیش از اکنون', fi ='$date1 [[w:fi:BP (ajanlasku)|BP]]', fr ='$date1 [[w:fr:Avant le présent|avant le présent]]', hr ='$date1 [[w:hr:BP|prije današnjice]]', it ='$date1 [[w:it:Before Present|BP]]', ja ='$date1 [[w:ja:BP (年代測定)|BP]]', lv ='$date1 [[w:lv:Pirms mūsdienām|Pirms mūsdienām]]', mk ='$date1 [[w:mk:Пред сегашноста|п.с.]]', ml ='[[w:en:Before Present|ഇന്നത്തെ കാലത്തിനു മുമ്പ്]] $date1', nb ='$date1 [[w:no:Before Present|BP]]', nn ='$date1 [[w:nn:Before Present|BP]]', nl ='$date1 [[w:nl:Before Present|BP]]', no ='$date1 [[w:no:Before Present|BP]]', pl ='$date1 [[w:pl:Before Present|BP]]', pt ='$date1 [[w:pt:Antes do Presente|Antes do Presente]]', ru ='$date1 [[w:ru:До настоящего времени|до н. в.]]', sk ='$date1 [[w:sk:BP (čas)|BP]]', sl ='$date1 [[w:sl:pred sedanjostjo|p. s.]]', sr ='$date1 [[w:sr:Прије садашњости|Прије садашњости]]', sv ='$date1 [[w:sv:B.P.|B.P.]]', uk ='$date1 [[w:uk:Років тому|Років тому]]', ur ='$date1 قبل حاضر', vi ='$date1 [[w:en:Before Present|trước ngày nay]]', ['zh-hans'] ='[[w:zh:距今|距今]]$date1', ['zh-hant'] ='[[w:zh:距今|距今]]$date1', default='<span style="color:red">$date1 [[w:en:Before Present|BP]]</span>', }, } -- List of possible string inputs to "adj", "junction", "era", and "precision" fields -- Each term is matched to its synonym and the role: a - adjective, e - era -- p - precision, c - calendar and combos, r - certainty and j - conjunction p.Synonyms = { -- adjectives and prepositions ['quarter1']={'1quarter','a'}, ['1stquarter']={'1quarter','a'}, ['1st quarter']={'1quarter','a'}, ['first quarter']={'1quarter','a'}, ['1quarter']={'1quarter','a'}, ['quarter2']={'2quarter','a'}, ['2ndquarter']={'2quarter','a'}, ['2nd quarter']={'2quarter','a'}, ['second quarter']={'2quarter','a'}, ['2quarter']={'2quarter','a'}, ['quarter3']={'3quarter','a'}, ['3rdquarter']={'3quarter','a'}, ['3rd quarter']={'3quarter','a'}, ['third quarter']={'3quarter','a'}, ['3quarter']={'3quarter','a'}, ['quarter4']={'4quarter','a'}, ['4thquarter']={'4quarter','a'}, ['4th quarter']={'4quarter','a'}, ['fourth quarter']={'4quarter','a'}, ['4quarter']={'4quarter','a'}, ['spring']={'spring','a'}, ['summer']={'summer','a'}, ['fall']={'autumn','a'}, ['autumn']={'autumn','a'}, ['winter']={'winter','a'}, ['beginning']={'early','a'}, ['early']={'early','a'}, ['1half']={'firsthalf','a'}, ['1sthalf']={'firsthalf','a'}, ['1st half']={'firsthalf','a'}, ['firsthalf']={'firsthalf','a'}, ['first half']={'firsthalf','a'}, ['end']={'late','a'}, ['late']={'late','a'}, ['middle']={'mid','a'}, ['mid']={'mid','a'}, ['2half']={'secondhalf','a'}, ['2ndhalf']={'secondhalf','a'}, ['2nd half']={'secondhalf','a'}, ['secondhalf']={'secondhalf','a'}, ['second half']={'secondhalf','a'}, ['<']={'before','a'}, ['b']={'before','a'}, ['before']={'before','a'}, ['>']={'after','a'}, ['a']={'after','a'}, ['after']={'after','a'}, ['~']={'circa','a'}, ['c']={'circa','a'}, ['ca']={'circa','a'}, ['circa']={'circa','a'}, ['by']={'by','a'}, ['from']={'from','a'}, ['until']={'until','a'}, ['uncertain']={'uncertain','r'}, ['possibly']={'possibly','r'}, ['probably']={'probably','r'}, ['presumably']={'presumably','r'}, -- eras ['bc']={'bc','e'}, ['bce']={'bc','e'}, ['before present']={'bp','e'}, ['bp']={'bp','e'}, ['ce']={'ad','e'}, ['ad']={'ad','e'}, ['ah']={'ah','e'}, -- conjunctions ['&']={'and','j'}, ['and']={'and','j'}, ['or']={'or','j'}, ['-']={'from-until','j'}, ['–']={'from-until','j'}, ['from-until']={'from-until','j'}, ['between']={'between','j'}, ['circa2']={'circa2','a'}, -- precision ['day']={'day','p'}, ['month']={'month','p'}, ['year']={'year','p'}, ['s']={'decade','p'}, ['decade']={'decade','p'}, ['century']={'century','p'}, ['millennium']={'millennium','p'}, ['million']={'million','p'}, ['billion']={'billion','p'}, -- calendar ['julian']={'julian','c'}, ['islamic']={'islamic','c'}, ['hijri']={'islamic','c'}, -- "Turn of the ..." options are a combos which will be handled in similar way as calendars ['turn of the century']={'turn of the century','c'}, ['century turn']={'turn of the century','c'}, ['turn of the decade']={'turn of the decade','c'}, ['decade turn']={'turn of the decade','c'}, ['turn of the year']={'turn of the year','c'}, ['year turn']={'turn of the year','c'}, -- other dates which will be handled in similar way as calendars ['?']={'unknown','c'}, ['unknown']={'unknown','c'}, ['unknown year']={'year unknown','c'}, ['year unknown']={'year unknown','c'}, } return p dq6g8vtfaua8zgt63ytoeyms0jwcsm1 Module:ISOdate 828 65954 803603 634135 2019-01-24T01:40:44Z en>Mike Peel 0 Protected "[[Module:ISOdate]]": [[WP:High-risk templates|High-risk Lua module]] ([Edit=Require template editor access] (indefinite) [Move=Require template editor access] (indefinite)) 634134 Scribunto text/plain --[[ This module is intended for processing of date strings. Please do not modify this code without applying the changes first at Module:ISOdate/sandbox and testing at Module:ISOdate/sandbox/testcases and Module talk:ISOdate/sandbox/testcases. Authors and maintainers: * User:Parent5446 - original version of the function mimicking template:ISOdate * User:Jarekt - original version of the functions mimicking template:Date and template:ISOyear ]] local p = {} -- ======================================= -- === Dependencies ====================== -- ======================================= local D = require('Module:DateI18n') -- the enwp version of c:Module:Date --[[ ISOyear This function returns year part of date string. Usage: {{#invoke:ISOdate|ISOyear|target_string}} Parameters 1: The date string Error Handling: If the string does not look like it contain the year than the function will not return anything. That is the preferred treatment for the template:Creator which is the main (only?) template calling it. ]] function p.ISOyear( frame ) return p._ISOyear( frame.args[1] ) end function p._ISOyear( input ) if not input then return '' end input = mw.text.trim( input ) -- if empty string then return it if input == "" then return input end -- if number then return it if tonumber( input ) then return mw.ustring.format( '%04i', input ) end -- otherwise use regular expression match input = mw.ustring.match( input, '^+?(-?%d%d?%d?%d?)-' ) if input and tonumber( input ) then return mw.ustring.format( '%04i', input ) else return '' end end --[[ ISOdate This function is the core part of the ISOdate template. Usage: {{#invoke:ISOdate|ISOdate|target_string|lang=}} Parameters: 1: The date string lang: The language to display it in form: Language format (genitive, etc.) for some languages class: CSS class for the <time> node Error Handling: If the string does not look like it contain the proper ISO date than the function will return the original string. That is the preferred treatment for the template:Information (and similar templates) which calling it. ]] function p.ISOdate(frame) local datestr, succeded local args = frame.args if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then args.lang = frame:callParserFunction( "int", "lang" ) -- get user's chosen language end datestr, succeded = p._ISOdate( mw.text.trim(args[1]), args.lang, -- language args.case or '', -- allows to specify grammatical case for the month for languages that use them args.class or 'dtstart', -- allows to set the html class of the time node where the date is included. args.trim_year or '100-999' -- by default pad one and 2 digit years to be 4 digit long, while keeping 3 digit years as is ) return datestr end function p._ISOdate(datestr, lang, case, class, trim_year) -- pattern: regexp - regular expresion to test; dlen - number of date elements; tail = which element is a "tail" if any -- regexp hints: -- 1) Strings starting with "^" and ending with "$" indicate whole string match -- 2) optional tail part copied as-is and following the main parsed part of the date have to be separated from the date by a whitespace, so "(\s.+)?" local patterns = { -- strings starting with YYYY-MM-DD HH:MM:SS. Year 4 digits (if we know seconds than it was within the last 100 years), the rest 1-2 -- date and time can be separated by space or "T" and there could be a "Z" on the end indicating "Zulu" time zone {dlen=6, tail=7, regexp="^+?(%d%d%d%d)-(%d%d?)-(%d%d?)[ T](%d%d?):(%d%d?):(%d%d?)Z?(%s.*)"}, {dlen=6, tail=0, regexp="^+?(%d%d%d%d)-(%d%d?)-(%d%d?)[ T](%d%d?):(%d%d?):(%d%d?)Z?$"}, -- strings starting with YYYY-MM-DD HH:MM. Year 4 digits, the rest 1-2 -- (if one knows hour and minute than it was probably after a year 1000) {dlen=5, tail=6, regexp="^+?(%d%d%d%d)-(%d%d?)-(%d%d?)[ T](%d%d?):(%d%d?)(%s.+)"}, {dlen=5, tail=0, regexp="^+?(%d%d%d%d)-(%d%d?)-(%d%d?)[ T](%d%d?):(%d%d?)$"}, -- strings starting with YYYY-MM-DD. Year 1-4 digits, the rest 1-2 {dlen=3, tail=4, regexp="^+?(%d%d?%d?%d?)-(%d%d?)-(%d%d?)(%s.+)"}, {dlen=3, tail=0, regexp="^+?(%d%d?%d?%d?)-(%d%d?)-(%d%d?)$"}, -- strings starting with YYYY-MM. Year 3-4 digits, month 2 digits -- (want to avoit converting to dates strings like 10-5 = 5 {dlen=2, tail=3, regexp="^+?(%d%d%d%d?)-(%d%d)(%s.+)"}, -- if whole string is in YYYY-MM form: If Year 1-4 digits, month 1-2 digits {dlen=2, tail=0, regexp="^+?(%d%d?%d?%d?)-(%d%d?)$"}, -- string starts with a number -> it has to be 3 or 4 digit long to be a year {dlen=1, tail=2, regexp="^+?(%d%d%d%d?)(%s.+)"}, -- if whole string is a number (1-4 digit long) than it will be interpreted as a year {dlen=1, tail=0, regexp="^+?(%d%d?%d?%d?)$"}, } -- create datevec based on which variables are provided local datevec, tail, formatNum datevec, tail, formatNum = p.test_date_formats(datestr or '', patterns) if datevec[1]=='' or datevec[1]==nil then -- quickly return if datestr does not look like date (it could be a template) return datestr, false end -- call p._Date function to format date string local succeded, datestr2 succeded, datestr2 = pcall( D._Date, datevec, lang, case, class, trim_year) if succeded and datestr2~='' then return mw.text.trim( datestr2 .. tail), true else -- in case of errors return the original string return datestr, false end end function p.ISOdate_extended(frame) -- pattern: regexp - regular expresion to test; dlen - number of date elements; tail = which element is a "tail" if any -- regexp hints: -- 1) Strings starting with "^" and ending with "$" indicate whole string match -- 2) optional tail part copied as-is and following the main parsed part of the date have to be separated from the date by a whitespace, so "(\s.+)?" local datestr, succeded local args = frame.args if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then args.lang = frame:callParserFunction( "int", "lang" ) -- get user's chosen language end datestr, succeded = p._ISOdate( mw.text.trim(args[1]), args.lang, -- language args.case or '', -- allows to specify grammatical case for the month for languages that use them args.class or 'dtstart', -- allows to set the html class of the time node where the date is included. args.trim_year or '100-999' -- by default pad one and 2 digit years to be 4 digit long, while keeping 3 digit years as is ) if succeded then return datestr end local patterns = { -- Exended set of recognized formats: like MM/DD/YYYY {dlen=3, tail=4, regexp="^(%d%d?)[-./](%d%d?)[-./](%d%d%d%d)(%s.+)"}, {dlen=3, tail=0, regexp="^(%d%d?)[-./](%d%d?)[-./](%d%d%d%d)$"}, {dlen=3, tail=0, regexp="^(%d%d?)%s(%w+)%s(%d%d%d%d)$"}, {dlen=3, tail=0, regexp="^(%w+)%s(%d%d?),%s(%d%d%d%d)$"}, } local datevec, tail, formatNum, category = '' datevec, tail, formatNum = p.test_date_formats(frame.args[1], patterns) if formatNum==1 or formatNum==2 then vec = datevec; if tonumber(datevec[1])>12 then frame.args[1] = string.format('%04i-%02i-%02i', datevec[3], datevec[2], datevec[1] ) category = '[[Category:Date in DD/MM/YYYY format]]' return mw.text.trim( p.ISOdate(frame) .. tail); elseif tonumber(datevec[2])>12 then frame.args[1] = string.format('%04i-%02i-%02i', datevec[3], datevec[1], datevec[2] ) category = '[[Category:Date in MM/DD/YYYY format]]' return mw.text.trim( p.ISOdate(frame) .. tail); end elseif (formatNum==3 or formatNum==4) and (datevec[3]=='' or datevec[3]~=nil) then local str = mw.getCurrentFrame():callParserFunction( "#time", { 'Y-m-d', datestr} ) local vec = {str:match( "^(%d%d?%d?%d?)-(%d%d?)-(%d%d?)$" )} if vec and vec[1]~=nil then frame.args[1] = string.format('%04i-%02i-%02i', vec[1], vec[2], vec[3] ) category = '[[Category:Date in word format]]' return p.ISOdate(frame); end end return datestr end function p.test_date_formats(datestr, patterns) -- pattern: regexp - regular expresion to test; dlen - number of date elements; tail = which element is a "tail" if any local datevec = {'','','','','',''} local tail = '' local vec, pat local formatNum = 0 for i, pat in ipairs( patterns ) do vec = {datestr:match( pat.regexp )} if vec and vec[1]~=nil then for j=1,pat.dlen do datevec[j] = vec[j] end if pat.tail>0 and vec[pat.tail]~=nil then tail = mw.ustring.gsub(' ' .. vec[pat.tail], ' +', ' ') end formatNum = i break end end return datevec, tail, formatNum end return p nab04kxj0ywahk6drgfjdo6jbixhup7 803604 803603 2024-04-29T16:35:41Z en>Pppery 0 Protected "[[Module:ISOdate]]" ([Edit=Require template editor access] (indefinite) [Move=Require template editor access] (indefinite)) 634134 Scribunto text/plain --[[ This module is intended for processing of date strings. Please do not modify this code without applying the changes first at Module:ISOdate/sandbox and testing at Module:ISOdate/sandbox/testcases and Module talk:ISOdate/sandbox/testcases. Authors and maintainers: * User:Parent5446 - original version of the function mimicking template:ISOdate * User:Jarekt - original version of the functions mimicking template:Date and template:ISOyear ]] local p = {} -- ======================================= -- === Dependencies ====================== -- ======================================= local D = require('Module:DateI18n') -- the enwp version of c:Module:Date --[[ ISOyear This function returns year part of date string. Usage: {{#invoke:ISOdate|ISOyear|target_string}} Parameters 1: The date string Error Handling: If the string does not look like it contain the year than the function will not return anything. That is the preferred treatment for the template:Creator which is the main (only?) template calling it. ]] function p.ISOyear( frame ) return p._ISOyear( frame.args[1] ) end function p._ISOyear( input ) if not input then return '' end input = mw.text.trim( input ) -- if empty string then return it if input == "" then return input end -- if number then return it if tonumber( input ) then return mw.ustring.format( '%04i', input ) end -- otherwise use regular expression match input = mw.ustring.match( input, '^+?(-?%d%d?%d?%d?)-' ) if input and tonumber( input ) then return mw.ustring.format( '%04i', input ) else return '' end end --[[ ISOdate This function is the core part of the ISOdate template. Usage: {{#invoke:ISOdate|ISOdate|target_string|lang=}} Parameters: 1: The date string lang: The language to display it in form: Language format (genitive, etc.) for some languages class: CSS class for the <time> node Error Handling: If the string does not look like it contain the proper ISO date than the function will return the original string. That is the preferred treatment for the template:Information (and similar templates) which calling it. ]] function p.ISOdate(frame) local datestr, succeded local args = frame.args if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then args.lang = frame:callParserFunction( "int", "lang" ) -- get user's chosen language end datestr, succeded = p._ISOdate( mw.text.trim(args[1]), args.lang, -- language args.case or '', -- allows to specify grammatical case for the month for languages that use them args.class or 'dtstart', -- allows to set the html class of the time node where the date is included. args.trim_year or '100-999' -- by default pad one and 2 digit years to be 4 digit long, while keeping 3 digit years as is ) return datestr end function p._ISOdate(datestr, lang, case, class, trim_year) -- pattern: regexp - regular expresion to test; dlen - number of date elements; tail = which element is a "tail" if any -- regexp hints: -- 1) Strings starting with "^" and ending with "$" indicate whole string match -- 2) optional tail part copied as-is and following the main parsed part of the date have to be separated from the date by a whitespace, so "(\s.+)?" local patterns = { -- strings starting with YYYY-MM-DD HH:MM:SS. Year 4 digits (if we know seconds than it was within the last 100 years), the rest 1-2 -- date and time can be separated by space or "T" and there could be a "Z" on the end indicating "Zulu" time zone {dlen=6, tail=7, regexp="^+?(%d%d%d%d)-(%d%d?)-(%d%d?)[ T](%d%d?):(%d%d?):(%d%d?)Z?(%s.*)"}, {dlen=6, tail=0, regexp="^+?(%d%d%d%d)-(%d%d?)-(%d%d?)[ T](%d%d?):(%d%d?):(%d%d?)Z?$"}, -- strings starting with YYYY-MM-DD HH:MM. Year 4 digits, the rest 1-2 -- (if one knows hour and minute than it was probably after a year 1000) {dlen=5, tail=6, regexp="^+?(%d%d%d%d)-(%d%d?)-(%d%d?)[ T](%d%d?):(%d%d?)(%s.+)"}, {dlen=5, tail=0, regexp="^+?(%d%d%d%d)-(%d%d?)-(%d%d?)[ T](%d%d?):(%d%d?)$"}, -- strings starting with YYYY-MM-DD. Year 1-4 digits, the rest 1-2 {dlen=3, tail=4, regexp="^+?(%d%d?%d?%d?)-(%d%d?)-(%d%d?)(%s.+)"}, {dlen=3, tail=0, regexp="^+?(%d%d?%d?%d?)-(%d%d?)-(%d%d?)$"}, -- strings starting with YYYY-MM. Year 3-4 digits, month 2 digits -- (want to avoit converting to dates strings like 10-5 = 5 {dlen=2, tail=3, regexp="^+?(%d%d%d%d?)-(%d%d)(%s.+)"}, -- if whole string is in YYYY-MM form: If Year 1-4 digits, month 1-2 digits {dlen=2, tail=0, regexp="^+?(%d%d?%d?%d?)-(%d%d?)$"}, -- string starts with a number -> it has to be 3 or 4 digit long to be a year {dlen=1, tail=2, regexp="^+?(%d%d%d%d?)(%s.+)"}, -- if whole string is a number (1-4 digit long) than it will be interpreted as a year {dlen=1, tail=0, regexp="^+?(%d%d?%d?%d?)$"}, } -- create datevec based on which variables are provided local datevec, tail, formatNum datevec, tail, formatNum = p.test_date_formats(datestr or '', patterns) if datevec[1]=='' or datevec[1]==nil then -- quickly return if datestr does not look like date (it could be a template) return datestr, false end -- call p._Date function to format date string local succeded, datestr2 succeded, datestr2 = pcall( D._Date, datevec, lang, case, class, trim_year) if succeded and datestr2~='' then return mw.text.trim( datestr2 .. tail), true else -- in case of errors return the original string return datestr, false end end function p.ISOdate_extended(frame) -- pattern: regexp - regular expresion to test; dlen - number of date elements; tail = which element is a "tail" if any -- regexp hints: -- 1) Strings starting with "^" and ending with "$" indicate whole string match -- 2) optional tail part copied as-is and following the main parsed part of the date have to be separated from the date by a whitespace, so "(\s.+)?" local datestr, succeded local args = frame.args if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then args.lang = frame:callParserFunction( "int", "lang" ) -- get user's chosen language end datestr, succeded = p._ISOdate( mw.text.trim(args[1]), args.lang, -- language args.case or '', -- allows to specify grammatical case for the month for languages that use them args.class or 'dtstart', -- allows to set the html class of the time node where the date is included. args.trim_year or '100-999' -- by default pad one and 2 digit years to be 4 digit long, while keeping 3 digit years as is ) if succeded then return datestr end local patterns = { -- Exended set of recognized formats: like MM/DD/YYYY {dlen=3, tail=4, regexp="^(%d%d?)[-./](%d%d?)[-./](%d%d%d%d)(%s.+)"}, {dlen=3, tail=0, regexp="^(%d%d?)[-./](%d%d?)[-./](%d%d%d%d)$"}, {dlen=3, tail=0, regexp="^(%d%d?)%s(%w+)%s(%d%d%d%d)$"}, {dlen=3, tail=0, regexp="^(%w+)%s(%d%d?),%s(%d%d%d%d)$"}, } local datevec, tail, formatNum, category = '' datevec, tail, formatNum = p.test_date_formats(frame.args[1], patterns) if formatNum==1 or formatNum==2 then vec = datevec; if tonumber(datevec[1])>12 then frame.args[1] = string.format('%04i-%02i-%02i', datevec[3], datevec[2], datevec[1] ) category = '[[Category:Date in DD/MM/YYYY format]]' return mw.text.trim( p.ISOdate(frame) .. tail); elseif tonumber(datevec[2])>12 then frame.args[1] = string.format('%04i-%02i-%02i', datevec[3], datevec[1], datevec[2] ) category = '[[Category:Date in MM/DD/YYYY format]]' return mw.text.trim( p.ISOdate(frame) .. tail); end elseif (formatNum==3 or formatNum==4) and (datevec[3]=='' or datevec[3]~=nil) then local str = mw.getCurrentFrame():callParserFunction( "#time", { 'Y-m-d', datestr} ) local vec = {str:match( "^(%d%d?%d?%d?)-(%d%d?)-(%d%d?)$" )} if vec and vec[1]~=nil then frame.args[1] = string.format('%04i-%02i-%02i', vec[1], vec[2], vec[3] ) category = '[[Category:Date in word format]]' return p.ISOdate(frame); end end return datestr end function p.test_date_formats(datestr, patterns) -- pattern: regexp - regular expresion to test; dlen - number of date elements; tail = which element is a "tail" if any local datevec = {'','','','','',''} local tail = '' local vec, pat local formatNum = 0 for i, pat in ipairs( patterns ) do vec = {datestr:match( pat.regexp )} if vec and vec[1]~=nil then for j=1,pat.dlen do datevec[j] = vec[j] end if pat.tail>0 and vec[pat.tail]~=nil then tail = mw.ustring.gsub(' ' .. vec[pat.tail], ' +', ' ') end formatNum = i break end end return datevec, tail, formatNum end return p nab04kxj0ywahk6drgfjdo6jbixhup7 803605 803604 2026-07-30T00:19:19Z SM7 3953 2 revisions imported from [[:en:Module:ISOdate]] 634134 Scribunto text/plain --[[ This module is intended for processing of date strings. Please do not modify this code without applying the changes first at Module:ISOdate/sandbox and testing at Module:ISOdate/sandbox/testcases and Module talk:ISOdate/sandbox/testcases. Authors and maintainers: * User:Parent5446 - original version of the function mimicking template:ISOdate * User:Jarekt - original version of the functions mimicking template:Date and template:ISOyear ]] local p = {} -- ======================================= -- === Dependencies ====================== -- ======================================= local D = require('Module:DateI18n') -- the enwp version of c:Module:Date --[[ ISOyear This function returns year part of date string. Usage: {{#invoke:ISOdate|ISOyear|target_string}} Parameters 1: The date string Error Handling: If the string does not look like it contain the year than the function will not return anything. That is the preferred treatment for the template:Creator which is the main (only?) template calling it. ]] function p.ISOyear( frame ) return p._ISOyear( frame.args[1] ) end function p._ISOyear( input ) if not input then return '' end input = mw.text.trim( input ) -- if empty string then return it if input == "" then return input end -- if number then return it if tonumber( input ) then return mw.ustring.format( '%04i', input ) end -- otherwise use regular expression match input = mw.ustring.match( input, '^+?(-?%d%d?%d?%d?)-' ) if input and tonumber( input ) then return mw.ustring.format( '%04i', input ) else return '' end end --[[ ISOdate This function is the core part of the ISOdate template. Usage: {{#invoke:ISOdate|ISOdate|target_string|lang=}} Parameters: 1: The date string lang: The language to display it in form: Language format (genitive, etc.) for some languages class: CSS class for the <time> node Error Handling: If the string does not look like it contain the proper ISO date than the function will return the original string. That is the preferred treatment for the template:Information (and similar templates) which calling it. ]] function p.ISOdate(frame) local datestr, succeded local args = frame.args if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then args.lang = frame:callParserFunction( "int", "lang" ) -- get user's chosen language end datestr, succeded = p._ISOdate( mw.text.trim(args[1]), args.lang, -- language args.case or '', -- allows to specify grammatical case for the month for languages that use them args.class or 'dtstart', -- allows to set the html class of the time node where the date is included. args.trim_year or '100-999' -- by default pad one and 2 digit years to be 4 digit long, while keeping 3 digit years as is ) return datestr end function p._ISOdate(datestr, lang, case, class, trim_year) -- pattern: regexp - regular expresion to test; dlen - number of date elements; tail = which element is a "tail" if any -- regexp hints: -- 1) Strings starting with "^" and ending with "$" indicate whole string match -- 2) optional tail part copied as-is and following the main parsed part of the date have to be separated from the date by a whitespace, so "(\s.+)?" local patterns = { -- strings starting with YYYY-MM-DD HH:MM:SS. Year 4 digits (if we know seconds than it was within the last 100 years), the rest 1-2 -- date and time can be separated by space or "T" and there could be a "Z" on the end indicating "Zulu" time zone {dlen=6, tail=7, regexp="^+?(%d%d%d%d)-(%d%d?)-(%d%d?)[ T](%d%d?):(%d%d?):(%d%d?)Z?(%s.*)"}, {dlen=6, tail=0, regexp="^+?(%d%d%d%d)-(%d%d?)-(%d%d?)[ T](%d%d?):(%d%d?):(%d%d?)Z?$"}, -- strings starting with YYYY-MM-DD HH:MM. Year 4 digits, the rest 1-2 -- (if one knows hour and minute than it was probably after a year 1000) {dlen=5, tail=6, regexp="^+?(%d%d%d%d)-(%d%d?)-(%d%d?)[ T](%d%d?):(%d%d?)(%s.+)"}, {dlen=5, tail=0, regexp="^+?(%d%d%d%d)-(%d%d?)-(%d%d?)[ T](%d%d?):(%d%d?)$"}, -- strings starting with YYYY-MM-DD. Year 1-4 digits, the rest 1-2 {dlen=3, tail=4, regexp="^+?(%d%d?%d?%d?)-(%d%d?)-(%d%d?)(%s.+)"}, {dlen=3, tail=0, regexp="^+?(%d%d?%d?%d?)-(%d%d?)-(%d%d?)$"}, -- strings starting with YYYY-MM. Year 3-4 digits, month 2 digits -- (want to avoit converting to dates strings like 10-5 = 5 {dlen=2, tail=3, regexp="^+?(%d%d%d%d?)-(%d%d)(%s.+)"}, -- if whole string is in YYYY-MM form: If Year 1-4 digits, month 1-2 digits {dlen=2, tail=0, regexp="^+?(%d%d?%d?%d?)-(%d%d?)$"}, -- string starts with a number -> it has to be 3 or 4 digit long to be a year {dlen=1, tail=2, regexp="^+?(%d%d%d%d?)(%s.+)"}, -- if whole string is a number (1-4 digit long) than it will be interpreted as a year {dlen=1, tail=0, regexp="^+?(%d%d?%d?%d?)$"}, } -- create datevec based on which variables are provided local datevec, tail, formatNum datevec, tail, formatNum = p.test_date_formats(datestr or '', patterns) if datevec[1]=='' or datevec[1]==nil then -- quickly return if datestr does not look like date (it could be a template) return datestr, false end -- call p._Date function to format date string local succeded, datestr2 succeded, datestr2 = pcall( D._Date, datevec, lang, case, class, trim_year) if succeded and datestr2~='' then return mw.text.trim( datestr2 .. tail), true else -- in case of errors return the original string return datestr, false end end function p.ISOdate_extended(frame) -- pattern: regexp - regular expresion to test; dlen - number of date elements; tail = which element is a "tail" if any -- regexp hints: -- 1) Strings starting with "^" and ending with "$" indicate whole string match -- 2) optional tail part copied as-is and following the main parsed part of the date have to be separated from the date by a whitespace, so "(\s.+)?" local datestr, succeded local args = frame.args if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then args.lang = frame:callParserFunction( "int", "lang" ) -- get user's chosen language end datestr, succeded = p._ISOdate( mw.text.trim(args[1]), args.lang, -- language args.case or '', -- allows to specify grammatical case for the month for languages that use them args.class or 'dtstart', -- allows to set the html class of the time node where the date is included. args.trim_year or '100-999' -- by default pad one and 2 digit years to be 4 digit long, while keeping 3 digit years as is ) if succeded then return datestr end local patterns = { -- Exended set of recognized formats: like MM/DD/YYYY {dlen=3, tail=4, regexp="^(%d%d?)[-./](%d%d?)[-./](%d%d%d%d)(%s.+)"}, {dlen=3, tail=0, regexp="^(%d%d?)[-./](%d%d?)[-./](%d%d%d%d)$"}, {dlen=3, tail=0, regexp="^(%d%d?)%s(%w+)%s(%d%d%d%d)$"}, {dlen=3, tail=0, regexp="^(%w+)%s(%d%d?),%s(%d%d%d%d)$"}, } local datevec, tail, formatNum, category = '' datevec, tail, formatNum = p.test_date_formats(frame.args[1], patterns) if formatNum==1 or formatNum==2 then vec = datevec; if tonumber(datevec[1])>12 then frame.args[1] = string.format('%04i-%02i-%02i', datevec[3], datevec[2], datevec[1] ) category = '[[Category:Date in DD/MM/YYYY format]]' return mw.text.trim( p.ISOdate(frame) .. tail); elseif tonumber(datevec[2])>12 then frame.args[1] = string.format('%04i-%02i-%02i', datevec[3], datevec[1], datevec[2] ) category = '[[Category:Date in MM/DD/YYYY format]]' return mw.text.trim( p.ISOdate(frame) .. tail); end elseif (formatNum==3 or formatNum==4) and (datevec[3]=='' or datevec[3]~=nil) then local str = mw.getCurrentFrame():callParserFunction( "#time", { 'Y-m-d', datestr} ) local vec = {str:match( "^(%d%d?%d?%d?)-(%d%d?)-(%d%d?)$" )} if vec and vec[1]~=nil then frame.args[1] = string.format('%04i-%02i-%02i', vec[1], vec[2], vec[3] ) category = '[[Category:Date in word format]]' return p.ISOdate(frame); end end return datestr end function p.test_date_formats(datestr, patterns) -- pattern: regexp - regular expresion to test; dlen - number of date elements; tail = which element is a "tail" if any local datevec = {'','','','','',''} local tail = '' local vec, pat local formatNum = 0 for i, pat in ipairs( patterns ) do vec = {datestr:match( pat.regexp )} if vec and vec[1]~=nil then for j=1,pat.dlen do datevec[j] = vec[j] end if pat.tail>0 and vec[pat.tail]~=nil then tail = mw.ustring.gsub(' ' .. vec[pat.tail], ' +', ' ') end formatNum = i break end end return datevec, tail, formatNum end return p nab04kxj0ywahk6drgfjdo6jbixhup7 Module:DateI18n 828 68207 803613 652109 2019-04-13T21:30:42Z commons>Jarekt 0 clone of Module:Date, rename since all the other projects use this name 803613 Scribunto text/plain --[[ This module is intended for processing of date strings. Please do not modify this code without applying the changes first at Module:Date/sandbox and testing at Module:Date/sandbox/testcases and Module talk:Date/sandbox/testcases. Authors and maintainers: * User:Parent5446 - original version of the function mimicking template:ISOdate * User:Jarekt - original version of the functions mimicking template:Date and template:ISOyear ]] local p = {} -- ======================================= -- === Dependencies ====================== -- ======================================= local i18n = require('Module:I18n/date') -- get localized translations of date formats local yesno = require('Module:Yesno') local function langSwitch(list,lang) local langList = mw.language.getFallbacksFor(lang) table.insert(langList,1,lang) for i,language in ipairs(langList) do if list[language] then return list[language] end end end --[[ Date This function is the core part of the ISOdate template. Usage: {{#invoke:Date|Date|year=|month=|day=|hour=|minute=|second=|tzhour=|tzmin=|lang=en}} Parameters: year,month,day,hour,minute,second: broken down date-time component strings tzhour, tzmin: timezone offset from UTC, hours and minutes lang: The language to display it in case: Language format (genitive, etc.) for some languages class: CSS class for the <time> node, use "" for no metadata at all Error Handling: ]] function p.Date(frame) local args = frame.args if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then args.lang = frame:callParserFunction( "int", "lang" ) -- get user's chosen language end return p._Date( { args.year or '', args.month or '', args.day or '', args.hour or '', args.minute or '', args.second or '', args.tzhour or '', args.tzmin or '' }, args.lang, -- language args.case or '', -- allows to specify grammatical case for the month for languages that use them args.class or 'dtstart', -- allows to set the html class of the time node where the date is included. This is useful for microformats. args.trim_year or '100-999' -- by default pad one and 2 digit years to be 4 digit long, while keeping 3 digit years as is ) end function p._Date(datevec, lang, case, class, trim_year) -- make sure inputs are in the right format for i = #datevec + 1, 8 do datevec[i] = '' end if not case then case = '' end if not class then class = '' end if not trim_year then trim_year = '100-999' end -- if language is not provided than look up users language -- WARNING: This step should be done by the template as it does not seem to work as well here (cache issues?) if not lang or not mw.language.isValidCode( lang ) then lang = 'en' end -- Just in case someone broke the internationalization code than fix the english defaults if i18n.DateLang['en'] == nil then i18n.DateLang['en'] = 'en-form' end if i18n.DateFormat['en-form'] == nil then i18n.DateFormat['en-form'] = {YMDHMS='j F Y, H:i:s', YMDHM='j F Y, H:i', YMD='j F Y', YM='F Y', MD='j F', Y='Y'} end -- create datecode based on which variables are provided and check for out of bound values local maxval = {9999, 12, 31, 23, 59, 60, 23, 59} -- max values for year, month, ... local c = {'Y', 'M', 'D', 'H', 'M', 'S', '', ''} local datecode = '' -- a string signifying which combination of variables was provided local datenum = {} -- date-time encoded as a vector = [year, month, ... , second] for i, v in ipairs( datevec ) do if v~=nil and v~='' then datecode = datecode .. c[i] datenum[i] = tonumber(v) if datenum[i]==nil and i==2 then -- month is not a number -> check if it is a month name in English v = mw.language.new('en'):formatDate( "n", v) datenum[i] = tonumber(v) end if datenum[i]==nil or datenum[i]>maxval[i] then -- Some numbers are out of range -> abort and return the empty string return '' end end end -- create time stamp string (for example 2000-02-20 02:20:20) based on which variables were provided local timeStamp if datecode == 'YMDHMS' then timeStamp = string.format('%04i-%02i-%02i %02i:%02i:%02i', datenum[1], datenum[2], datenum[3], datenum[4], datenum[5], datenum[6] ) elseif datecode == 'YMDHM' then timeStamp = string.format('%04i-%02i-%02i %02i:%02i', datenum[1], datenum[2], datenum[3], datenum[4], datenum[5] ) elseif datecode:sub(1,3)=='YMD' then timeStamp = string.format('%04i-%02i-%02i', datenum[1], datenum[2], datenum[3] ) datecode = 'YMD' -- 'YMD', 'YMDHMS' and 'YMDHM' are the only supported format starting with 'YMD'. All others will be converted to 'YMD' elseif datecode == 'YM' then timeStamp = string.format('%04i-%02i', datenum[1], datenum[2] ) elseif datecode:sub(1,1)=='Y' then timeStamp = string.format('%04i', datenum[1] ) datecode = 'Y' elseif datecode == 'M' then timeStamp = string.format('%04i-%02i-%02i', 2000, datenum[2], 1 ) class = '' -- date not complete -> no html formating or micro-tagging of date string elseif datecode == 'MD' then timeStamp = string.format('%04i-%02i-%02i', 2000, datenum[2], datenum[3] ) class = '' -- date not complete -> no html formating or micro-tagging of date string else return '' -- format not supported end -- ========================================================== -- === Create Date String using in chosen language -- ========================================================== -- which form should the date take? -- Use langSwitch to pick formating for each language local langDateForm = langSwitch(i18n.DateLang, lang) -- special case of French and Gallic dates, which require different date format for the 1st day of the month if datenum[3]==1 and (langDateForm=='fr-form' or langDateForm=='ga-form' or langDateForm=='ca-form') then langDateForm = langDateForm .. '1' -- ordinal form for the first day of the month end -- special case of Basque dates, which require different date format for the 1st, 11th, 21st and 31st day of the month if langDateForm=='eu-form' then if (datenum[3]==1 or datenum[3]==21) then langDateForm = 'eu-form01' elseif (datenum[3]==11 or datenum[3]==31) then langDateForm = 'eu-form11' end end -- Look up country specific format input to {{#time}} function local dFormat = i18n.DateFormat[langDateForm][datecode] -- overwrite default grammatical case of the month (applies mostly to Slavic languages) if (case=='gen') then -- CAUTION: at the moment i18n.DateFormat uses "F" only as month name, but this might change and this operation does not check if 'F' is in "" brackets or not, so if some language starts using 'F' in "" than this will not work for that language dFormat = dFormat:gsub("F", "xg"); end if (case=='nom') then -- CAUTION: at the moment i18n.DateFormat uses "xg" only as month name, but this might change and this operation does not check if 'xg' is in "" brackets or not, so if some language starts using 'xg' in "" than this will not work for that language dFormat = dFormat:gsub("xg", "F"); end if ((lang=='ru' or lang=='pl' or lang=='cs' or lang=='sl' or lang=='sk') and (case=='loc' or case=='ins')) or (lang=='fi' and (case=='ptv' or case=='ine'or case=='ela'or case=='ill') ) then local monthEn = mw.language.new('en'):formatDate( "F", timeStamp) -- month name in English -- month name using proper case and language. It relies on messages stored in MediaWiki namespace for some cases and languages -- That is why this IF statement uses "lang" not "langDateForm" variable to decide local monthMsg = mw.message.new( string.format('%s-%s', monthEn, case ) ):inLanguage( lang ) if not monthMsg:isDisabled() then -- make sure it exists local month=monthMsg:plain() dFormat = dFormat:gsub('F', '"'..month..'"'); -- replace default month with month name we already looked up dFormat = dFormat:gsub('xg', '"'..month..'"'); end end -- Special case related to Quechua and Kichwa languages -- see https://commons.wikimedia.org/wiki/Template_talk:Date#Quechua from 2014 if (lang=='qu' or lang=='qug') and case=='nom' then dFormat = dFormat:gsub('F"pi"', 'F'); end -- Lua only date formating using {{#time}} parser function (new) -- prefered call which gives "Lua error: too many language codes requested." on the [[Module talk:Date/sandbox/testcases]] page --local datestr = mw.language.new(lang):formatDate( dFormat, timeStamp) local datestr = mw.getCurrentFrame():callParserFunction( "#time", { dFormat, timeStamp, lang } ) -- Another special case related to Thai solar calendar if lang=='th' and datenum[1]~= nil and datenum[1]<=1940 then -- As of 2014 {{#time}} parser function did not resolve those cases properly -- See https://en.wikipedia.org/wiki/Thai_solar_calendar#New_year for reference -- Disable once https://bugzilla.wikimedia.org/show_bug.cgi?id=66648 is fixed if datecode=='Y' then -- date is ambiguous datestr = string.format('%04i หรือ %04i', datenum[1]+542, datenum[1]+543 ) elseif datenum[2]<=3 then -- year is wrong (one too many) datestr = datestr:gsub( string.format('%04i', datenum[1]+543), string.format('%04i', datenum[1]+542 ) ) end end -- If year<1000 than either keep it padded to the length of 4 digits or trim it -- decide if the year will stay padded with zeros (for years in 0-999 range) if datenum[1]~= nil and datenum[1]<1000 then local trim = yesno(trim_year,nil) if trim == nil then local YMin, YMax = trim_year:match( '(%d+)-(%d+)' ) trim = (YMin~=nil and datenum[1]>=tonumber(YMin) and datenum[1]<=tonumber(YMax)) end -- If the date form isn't the Thai solar calendar, don't zero pad years in the range of 100-999. -- If at some point support for Islamic/Hebrew/Japanese years is added, they may need to be skipped as well. if trim then --local yearStr1 = mw.language.new(lang):formatDate( 'Y', timeStamp) local yearStr1 = mw.getCurrentFrame():callParserFunction( "#time", { 'Y', timeStamp, lang } ) --local yearStr1 = datestr:match( '%d%d%d%d' ) -- 4 digits in a row (in any language) - that must be a year local yearStr2 = yearStr1 local zeroStr = mw.ustring.sub(yearStr1,1,1) for i=1,3 do -- trim leading zeros if mw.ustring.sub(yearStr2,1,1)==zeroStr then yearStr2 = mw.ustring.sub(yearStr2, 2, 5-i) else break end end datestr = datestr:gsub( yearStr1, yearStr2 ) --datestr = string.format('%s (%s, %s)', datestr, yearStr1, yearStr2 ) end end -- append timezone if present if datevec[7] ~= '' and (datecode == 'YMDHMS' or datecode == 'YMDHM') then local tzstr, tzhournum = '', tonumber(datevec[7]) if tzhournum < 0 then tzstr = '−' else tzstr = '+' end tzstr = tzstr..string.format("%02d", math.abs(tzhournum))..':' if datevec[8] ~= '' then tzstr = tzstr..datevec[8] else tzstr = tzstr..'00' end datestr = datestr..' '..tzstr end -- html formating and tagging of date string if class ~= '' then local DateHtmlTags = '<span style="white-space:nowrap"><time class="%s" datetime="%s">%s</time></span>' datestr = DateHtmlTags:format(class, timeStamp, datestr) end return datestr end return p n2fu3b16bq7r4kqhe08ddyc1nwz1tq8 803614 803613 2019-04-13T21:39:27Z commons>Jarekt 0 803614 Scribunto text/plain --[[ __ __ _ _ ____ _ ___ _ ___ | \/ | ___ __| |_ _| | ___ _| _ \ __ _| |_ ___|_ _/ |( _ ) _ __ | |\/| |/ _ \ / _` | | | | |/ _ (_) | | |/ _` | __/ _ \| || |/ _ \| '_ \ | | | | (_) | (_| | |_| | | __/_| |_| | (_| | || __/| || | (_) | | | | |_| |_|\___/ \__,_|\__,_|_|\___(_)____/ \__,_|\__\___|___|_|\___/|_| |_| This module is intended for processing of date strings. Please do not modify this code without applying the changes first at Module:Date/sandbox and testing at Module:Date/sandbox/testcases and Module talk:Date/sandbox/testcases. Authors and maintainers: * User:Parent5446 - original version of the function mimicking template:ISOdate * User:Jarekt - original version of the functions mimicking template:Date and template:ISOyear ]] local p = {} -- ======================================= -- === Dependencies ====================== -- ======================================= local i18n = require('Module:I18n/date') -- get localized translations of date formats local yesno = require('Module:Yesno') local function langSwitch(list,lang) local langList = mw.language.getFallbacksFor(lang) table.insert(langList,1,lang) for i,language in ipairs(langList) do if list[language] then return list[language] end end end --[[ Date This function is the core part of the ISOdate template. Usage: {{#invoke:DateI18n|Date|year=|month=|day=|hour=|minute=|second=|tzhour=|tzmin=|lang=en}} Parameters: year,month,day,hour,minute,second: broken down date-time component strings tzhour, tzmin: timezone offset from UTC, hours and minutes lang: The language to display it in case: Language format (genitive, etc.) for some languages class: CSS class for the <time> node, use "" for no metadata at all Error Handling: ]] function p.Date(frame) local args = frame.args if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then args.lang = frame:callParserFunction( "int", "lang" ) -- get user's chosen language end return p._Date( { args.year or '', args.month or '', args.day or '', args.hour or '', args.minute or '', args.second or '', args.tzhour or '', args.tzmin or '' }, args.lang, -- language args.case or '', -- allows to specify grammatical case for the month for languages that use them args.class or 'dtstart', -- allows to set the html class of the time node where the date is included. This is useful for microformats. args.trim_year or '100-999' -- by default pad one and 2 digit years to be 4 digit long, while keeping 3 digit years as is ) end function p._Date(datevec, lang, case, class, trim_year) -- make sure inputs are in the right format for i = #datevec + 1, 8 do datevec[i] = '' end if not case then case = '' end if not class then class = '' end if not trim_year then trim_year = '100-999' end -- if language is not provided than look up users language -- WARNING: This step should be done by the template as it does not seem to work as well here (cache issues?) if not lang or not mw.language.isValidCode( lang ) then lang = 'en' end -- Just in case someone broke the internationalization code than fix the english defaults if i18n.DateLang['en'] == nil then i18n.DateLang['en'] = 'en-form' end if i18n.DateFormat['en-form'] == nil then i18n.DateFormat['en-form'] = {YMDHMS='j F Y, H:i:s', YMDHM='j F Y, H:i', YMD='j F Y', YM='F Y', MD='j F', Y='Y'} end -- create datecode based on which variables are provided and check for out of bound values local maxval = {9999, 12, 31, 23, 59, 60, 23, 59} -- max values for year, month, ... local c = {'Y', 'M', 'D', 'H', 'M', 'S', '', ''} local datecode = '' -- a string signifying which combination of variables was provided local datenum = {} -- date-time encoded as a vector = [year, month, ... , second] for i, v in ipairs( datevec ) do if v~=nil and v~='' then datecode = datecode .. c[i] datenum[i] = tonumber(v) if datenum[i]==nil and i==2 then -- month is not a number -> check if it is a month name in English v = mw.language.new('en'):formatDate( "n", v) datenum[i] = tonumber(v) end if datenum[i]==nil or datenum[i]>maxval[i] then -- Some numbers are out of range -> abort and return the empty string return '' end end end -- create time stamp string (for example 2000-02-20 02:20:20) based on which variables were provided local timeStamp if datecode == 'YMDHMS' then timeStamp = string.format('%04i-%02i-%02i %02i:%02i:%02i', datenum[1], datenum[2], datenum[3], datenum[4], datenum[5], datenum[6] ) elseif datecode == 'YMDHM' then timeStamp = string.format('%04i-%02i-%02i %02i:%02i', datenum[1], datenum[2], datenum[3], datenum[4], datenum[5] ) elseif datecode:sub(1,3)=='YMD' then timeStamp = string.format('%04i-%02i-%02i', datenum[1], datenum[2], datenum[3] ) datecode = 'YMD' -- 'YMD', 'YMDHMS' and 'YMDHM' are the only supported format starting with 'YMD'. All others will be converted to 'YMD' elseif datecode == 'YM' then timeStamp = string.format('%04i-%02i', datenum[1], datenum[2] ) elseif datecode:sub(1,1)=='Y' then timeStamp = string.format('%04i', datenum[1] ) datecode = 'Y' elseif datecode == 'M' then timeStamp = string.format('%04i-%02i-%02i', 2000, datenum[2], 1 ) class = '' -- date not complete -> no html formating or micro-tagging of date string elseif datecode == 'MD' then timeStamp = string.format('%04i-%02i-%02i', 2000, datenum[2], datenum[3] ) class = '' -- date not complete -> no html formating or micro-tagging of date string else return '' -- format not supported end -- ========================================================== -- === Create Date String using in chosen language -- ========================================================== -- which form should the date take? -- Use langSwitch to pick formating for each language local langDateForm = langSwitch(i18n.DateLang, lang) -- special case of French and Gallic dates, which require different date format for the 1st day of the month if datenum[3]==1 and (langDateForm=='fr-form' or langDateForm=='ga-form' or langDateForm=='ca-form') then langDateForm = langDateForm .. '1' -- ordinal form for the first day of the month end -- special case of Basque dates, which require different date format for the 1st, 11th, 21st and 31st day of the month if langDateForm=='eu-form' then if (datenum[3]==1 or datenum[3]==21) then langDateForm = 'eu-form01' elseif (datenum[3]==11 or datenum[3]==31) then langDateForm = 'eu-form11' end end -- Look up country specific format input to {{#time}} function local dFormat = i18n.DateFormat[langDateForm][datecode] -- overwrite default grammatical case of the month (applies mostly to Slavic languages) if (case=='gen') then -- CAUTION: at the moment i18n.DateFormat uses "F" only as month name, but this might change and this operation does not check if 'F' is in "" brackets or not, so if some language starts using 'F' in "" than this will not work for that language dFormat = dFormat:gsub("F", "xg"); end if (case=='nom') then -- CAUTION: at the moment i18n.DateFormat uses "xg" only as month name, but this might change and this operation does not check if 'xg' is in "" brackets or not, so if some language starts using 'xg' in "" than this will not work for that language dFormat = dFormat:gsub("xg", "F"); end if ((lang=='ru' or lang=='pl' or lang=='cs' or lang=='sl' or lang=='sk') and (case=='loc' or case=='ins')) or (lang=='fi' and (case=='ptv' or case=='ine'or case=='ela'or case=='ill') ) then local monthEn = mw.language.new('en'):formatDate( "F", timeStamp) -- month name in English -- month name using proper case and language. It relies on messages stored in MediaWiki namespace for some cases and languages -- That is why this IF statement uses "lang" not "langDateForm" variable to decide local monthMsg = mw.message.new( string.format('%s-%s', monthEn, case ) ):inLanguage( lang ) if not monthMsg:isDisabled() then -- make sure it exists local month=monthMsg:plain() dFormat = dFormat:gsub('F', '"'..month..'"'); -- replace default month with month name we already looked up dFormat = dFormat:gsub('xg', '"'..month..'"'); end end -- Special case related to Quechua and Kichwa languages -- see https://commons.wikimedia.org/wiki/Template_talk:Date#Quechua from 2014 if (lang=='qu' or lang=='qug') and case=='nom' then dFormat = dFormat:gsub('F"pi"', 'F'); end -- Lua only date formating using {{#time}} parser function (new) -- prefered call which gives "Lua error: too many language codes requested." on the [[Module talk:Date/sandbox/testcases]] page --local datestr = mw.language.new(lang):formatDate( dFormat, timeStamp) local datestr = mw.getCurrentFrame():callParserFunction( "#time", { dFormat, timeStamp, lang } ) -- Another special case related to Thai solar calendar if lang=='th' and datenum[1]~= nil and datenum[1]<=1940 then -- As of 2014 {{#time}} parser function did not resolve those cases properly -- See https://en.wikipedia.org/wiki/Thai_solar_calendar#New_year for reference -- Disable once https://bugzilla.wikimedia.org/show_bug.cgi?id=66648 is fixed if datecode=='Y' then -- date is ambiguous datestr = string.format('%04i หรือ %04i', datenum[1]+542, datenum[1]+543 ) elseif datenum[2]<=3 then -- year is wrong (one too many) datestr = datestr:gsub( string.format('%04i', datenum[1]+543), string.format('%04i', datenum[1]+542 ) ) end end -- If year<1000 than either keep it padded to the length of 4 digits or trim it -- decide if the year will stay padded with zeros (for years in 0-999 range) if datenum[1]~= nil and datenum[1]<1000 then local trim = yesno(trim_year,nil) if trim == nil then local YMin, YMax = trim_year:match( '(%d+)-(%d+)' ) trim = (YMin~=nil and datenum[1]>=tonumber(YMin) and datenum[1]<=tonumber(YMax)) end -- If the date form isn't the Thai solar calendar, don't zero pad years in the range of 100-999. -- If at some point support for Islamic/Hebrew/Japanese years is added, they may need to be skipped as well. if trim then --local yearStr1 = mw.language.new(lang):formatDate( 'Y', timeStamp) local yearStr1 = mw.getCurrentFrame():callParserFunction( "#time", { 'Y', timeStamp, lang } ) --local yearStr1 = datestr:match( '%d%d%d%d' ) -- 4 digits in a row (in any language) - that must be a year local yearStr2 = yearStr1 local zeroStr = mw.ustring.sub(yearStr1,1,1) for i=1,3 do -- trim leading zeros if mw.ustring.sub(yearStr2,1,1)==zeroStr then yearStr2 = mw.ustring.sub(yearStr2, 2, 5-i) else break end end datestr = datestr:gsub( yearStr1, yearStr2 ) --datestr = string.format('%s (%s, %s)', datestr, yearStr1, yearStr2 ) end end -- append timezone if present if datevec[7] ~= '' and (datecode == 'YMDHMS' or datecode == 'YMDHM') then local tzstr, tzhournum = '', tonumber(datevec[7]) if tzhournum < 0 then tzstr = '−' else tzstr = '+' end tzstr = tzstr..string.format("%02d", math.abs(tzhournum))..':' if datevec[8] ~= '' then tzstr = tzstr..datevec[8] else tzstr = tzstr..'00' end datestr = datestr..' '..tzstr end -- html formating and tagging of date string if class ~= '' then local DateHtmlTags = '<span style="white-space:nowrap"><time class="%s" datetime="%s">%s</time></span>' datestr = DateHtmlTags:format(class, timeStamp, datestr) end return datestr end return p bmb66i0znfhfpeiuqiilu43abfhwhg5 803615 803614 2020-04-12T12:23:16Z commons>Jarekt 0 use [[Data:DateI18n.tab]] as main repository of language dependent settings 803615 Scribunto text/plain --[[ __ __ _ _ ____ _ ___ _ ___ | \/ | ___ __| |_ _| | ___ _| _ \ __ _| |_ ___|_ _/ |( _ ) _ __ | |\/| |/ _ \ / _` | | | | |/ _ (_) | | |/ _` | __/ _ \| || |/ _ \| '_ \ | | | | (_) | (_| | |_| | | __/_| |_| | (_| | || __/| || | (_) | | | | |_| |_|\___/ \__,_|\__,_|_|\___(_)____/ \__,_|\__\___|___|_|\___/|_| |_| This module is intended for processing of date strings. Please do not modify this code without applying the changes first at Module:Date/sandbox and testing at Module:Date/sandbox/testcases and Module talk:Date/sandbox/testcases. Authors and maintainers: * User:Parent5446 - original version of the function mimicking template:ISOdate * User:Jarekt - original version of the functions mimicking template:Date ]] require('Module:No globals') -- ================================================== -- === Internal functions =========================== -- ================================================== -- Function allowing for consistent treatment of boolean-like wikitext input. -- It works similarly to Module:Yesno local function yesno(val, default) if type(val) == 'boolean' then return val elseif type(val) == 'number' then if val==1 then return true elseif val==0 then return false end elseif type(val) == 'string' then val = mw.ustring.lower(val) -- put in lower case if val == 'no' or val == 'n' or val == 'false' or tonumber(val) == 0 then return false elseif val == 'yes' or val == 'y' or val == 'true' or tonumber(val) == 1 then return true end end return default end --------------------------------------------------------------------------------------- -- String replacement that ignores part of the string in "..." local function strReplace(String, old, new) if String:find('"') then local T={} for i, str in ipairs(mw.text.split( String, '"', true )) do if i%2==1 then str = str:gsub(old, new) end table.insert(T, str) end return table.concat(T,'"') else return String:gsub(old, new) end end --------------------------------------------------------------------------------------- -- process datevec -- INPUT: -- * datevec - Array of {year,month,day,hour,minute,second, tzhour, tzmin} containing broken -- down date-time component strings or numbers -- OUTPUT: -- * datecode - a code specifying content of the array where Y' is year, 'M' is month, -- 'D' is day, 'H' is hour, 'M' minute, 'S' is second. output has to be one of YMDHMS, YMDHM, YMD, YM, MD, Y -- * datenum - same array but holding only numbers or nuls local function parserDatevec(datevec) -- if month is not a number than check if it is a month name in project's language local month = datevec[2] if month and not tonumber(month) then datevec[2] = mw.getContentLanguage():formatDate( "n", month) end -- create datecode based on which variables are provided and check for out-of-bound values local maxval = {nil, 12, 31, 23, 59, 59, 23, 59} -- max values for year, month, ... local minval = {nil, 1, 1, 1, 1, 1, -23, 1} -- min values for year, month, ... local c = {'Y', 'M', 'D', 'H', 'M', 'S', '', ''} local datecode = '' -- a string signifying which combination of variables was provided local datenum = {} -- date-time encoded as a vector = [year, month, ... , second] for i = 1,8 do datenum[i] = tonumber(datevec[i]) if datenum[i] and (i==1 or (datenum[i]>=minval[i] and datenum[i]<=maxval[i])) then datecode = datecode .. c[i] end end return datecode, datenum end --------------------------------------------------------------------------------------- -- process datevec -- INPUT: -- * datecode - a code specifying content of the array where Y' is year, 'M' is month, -- 'D' is day, 'H' is hour, 'M' minute, 'S' is second. output has to be one of YMDHMS, YMDHM, YMD, YM, MD, Y -- * datenum - Array of {year,month,day,hour,minute,second, tzhour, tzmin} as numbers or nuls -- OUTPUT: -- * timeStamp - date string in the format taken by mw.language:formatDate lua function and {{#time}} perser function -- https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#mw.language:formatDate -- https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions#.23time -- * datecode - with possible corrections local function getTimestamp(datecode, datenum) -- create time stamp string (for example 2000-02-20 02:20:20) based on which variables were provided local timeStamp if datecode == 'YMDHMS' then timeStamp = string.format('%04i-%02i-%02i %02i:%02i:%02i', datenum[1], datenum[2], datenum[3], datenum[4], datenum[5], datenum[6] ) elseif datecode == 'YMDHM' then timeStamp = string.format('%04i-%02i-%02i %02i:%02i', datenum[1], datenum[2], datenum[3], datenum[4], datenum[5] ) elseif datecode:sub(1,3)=='YMD' then timeStamp = string.format('%04i-%02i-%02i', datenum[1], datenum[2], datenum[3] ) datecode = 'YMD' -- 'YMD', 'YMDHMS' and 'YMDHM' are the only supported format starting with 'YMD'. All others will be converted to 'YMD' elseif datecode == 'YM' then timeStamp = string.format('%04i-%02i', datenum[1], datenum[2] ) elseif datecode:sub(1,1)=='Y' then timeStamp = string.format('%04i', datenum[1] ) datecode = 'Y' elseif datecode == 'M' then timeStamp = string.format('%04i-%02i-%02i', 2000, datenum[2], 1 ) elseif datecode == 'MD' then timeStamp = string.format('%04i-%02i-%02i', 2000, datenum[2], datenum[3] ) else timeStamp = nil -- format not supported end return timeStamp, datecode end --------------------------------------------------------------------------------------- -- trim leading zeros in years prior to year 1000 -- INPUT: -- * datestr - translated date string -- * lang - language of translation -- OUTPUT: -- * datestr - updated date string local function trimYear(datestr, year, lang) local yearStr0, yearStr1, yearStr2, zeroStr yearStr0 = string.format('%04i', year ) -- 4 digit year in standard form "0123" yearStr1 = mw.language.new(lang):formatDate( 'Y', yearStr0) -- same as calling {{#time}} parser function --yearStr1 = mw.getCurrentFrame():callParserFunction( "#time", { 'Y', yearStr0, lang } ) -- translate to a language if yearStr0==yearStr1 then -- most of languages use standard form of year yearStr2 = tostring(year) else -- some languages use different characters for numbers yearStr2 = yearStr1 zeroStr = mw.ustring.sub(yearStr1,1,1) -- get "0" in whatever language for i=1,3 do -- trim leading zeros if mw.ustring.sub(yearStr2,1,1)==zeroStr then yearStr2 = mw.ustring.sub(yearStr2, 2, 5-i) else break end end end return string.gsub(datestr, yearStr1, yearStr2 ) -- in datestr replace long year with trimmed one end --------------------------------------------------------------------------------------- -- Look up proper format string to be passed to {{#time}} parser function -- INPUTS: -- * datecode: YMDHMS, YMDHM, YMD, YM, MD, Y, or M -- * day : Number between 1 and 31 (not needed for most languages) -- * lang : language -- OUTPUT: -- * dFormat : input to {{#time}} function local function getDateFormat(datecode, day, lang) local function parseFormat(dFormat, day) if dFormat:find('default') and #dFormat>10 then -- special (and messy) case of dFormat code depending on a day number -- then json contains a string with more json containing "default" field and 2 digit day keys -- if desired day is not in that json than use "default" case dFormat = dFormat:gsub('”','"') -- change fancy double quote to a straight one, used for json marking local D = mw.text.jsonDecode( dFormat ) --com = mw.dumpObject(D) day = string.format('d%02i',day) -- create day key dFormat = D[day] or D.default dFormat = dFormat:gsub("'", '"') -- change single quote to a double quote, used for {{#time}} marking end return dFormat end local T = {} local tab = mw.ext.data.get('DateI18n.tab', lang) for _, row in pairs(tab.data) do -- convert the output into a dictionary table local id, _, msg = unpack(row) T[id] = msg end local dFormat = T[datecode] if dFormat=='default' and (datecode=='YMDHMS' or datecode=='YMDHM') then -- for most languages adding hour:minute:second is done by adding ", HH:MM:SS to the -- day precission date, those languages are skipped in DateI18n.tab and default to -- English which stores word "default" dFormat = parseFormat(T['YMD'], day).. ', H:i' if datecode=='YMDHMS' then dFormat = dFormat .. ':s' end else dFormat = parseFormat(dFormat, day) end return dFormat end --------------------------------------------------------------------------------------- -- Look up proper format string to be passed to {{#time}} parser function -- INPUTS: -- * month : month number -- * case : gramatic case abbriviation, like "ins", "loc" -- * lang : language -- OUTPUT: -- * dFormat : input to {{#time}} function local function MonthCase(month, case, lang) local T = {{},{},{},{},{},{},{},{},{},{},{},{}} local tab = mw.ext.data.get('I18n/MonthCases.tab', lang) for _, row in pairs(tab.data) do local mth, cs, msg = unpack(row) T[mth][cs] = msg end return T[month][case] end -- ================================================== -- === External functions =========================== -- ================================================== local p = {} --[[ ======================================================================================== Date This function is the core part of the ISOdate template. Usage: local Date = require('Module:DateI18n')._Date local dateStr = Date({2020, 12, 30, 12, 20, 11}, lang) Parameters: * {year,month,day,hour,minute,second, tzhour, tzmin}: broken down date-time component strings or numbers tzhour, tzmin are timezone offsets from UTC, hours and minutes * lang: The language to display it in * case: Language format (genitive, etc.) for some languages * class: CSS class for the <time> node, use "" for no metadata at all ]] function p._Date(datevec, lang, case, class, trim_year) -- make sure inputs are in the right format if not lang or not mw.language.isValidCode( lang ) then lang = mw.getCurrentFrame():callParserFunction( "int", "lang" ) -- get user's chosen language end if lang == 'be-tarsk' then lang = 'be-x-old' end -- process datevec and extract timeStamp and datecode strings as well as numeric datenum array local datecode, datenum = parserDatevec(datevec) local year, month, day = datenum[1], datenum[2], datenum[3] local timeStamp, datecode = getTimestamp(datecode, datenum) if not timeStamp then -- something went wrong in parserDatevec return '' end -- Commons [[Data:DateI18n.tab]] page stores prefered formats for diferent -- languages and datecodes (specifying year-month-day or just year of month-day, etc) -- Look up country specific format input to {{#time}} function local dFormat = getDateFormat(datecode, day, lang) -- By default the gramatical case is not specified (case=='') allowing the format to be specified -- in [[Data:DateI18n.tab]]. You can overwrite the default grammatical case of the month by -- specifying "case" variable. This is needed mostly by Slavic languages to create more complex -- phrases as it is done in [[c:Module:Complex date]] case = case or '' if (lang=='qu' or lang=='qug') and (case=='nom') then -- Special case related to Quechua and Kichwa languages. The form in the I18n is -- Genitive case with suffix "pi" added to month names provided by {#time}} -- in Nominative case that "pi" should be removed -- see https://commons.wikimedia.org/wiki/Template_talk:Date#Quechua from 2014 dFormat = dFormat:gsub('F"pi"', 'F') elseif (case=='gen') then dFormat = strReplace(dFormat, "F", "xg") elseif (case=='nom') then dFormat = strReplace(dFormat, "xg", "F") elseif (case ~= '') then -- see is page [[Data:I18n/MonthCases.tab]] on Commons have name of the month -- in specific gramatic case in desired language. If we have it than replace -- "F" and xg" in dFormat local monthMsg = MonthCase(month, case, lang) if monthMsg and monthMsg ~= '' then -- make sure it exists dFormat = strReplace(dFormat, 'F', '"'..monthMsg..'"') -- replace default month with month name we already looked up dFormat = strReplace(dFormat, 'xg', '"'..monthMsg..'"') end end -- Translate the date using specified format -- See https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#mw.language:formatDate and -- https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions##time for explanation of the format local datestr = mw.language.new(lang):formatDate( dFormat, timeStamp) -- same as using {{#time}} parser function -- Special case related to Thai solar calendar: prior to 1940 new-year was at different -- time of year, so just year (datecode=='Y') is ambiguous and is replaced by "YYYY or YYYY" phrase if lang=='th' and datecode=='Y' and year<=1940 then datestr = string.format('%04i หรือ %04i', year+542, year+543 ) end -- If year<1000 than either keep the date padded to the length of 4 digits or trim it -- decide if the year will stay padded with zeros (for years in 0-999 range) if year and year<1000 then if type(trim_year)=='nil' then trim_year = '100-999' end local trim = yesno(trim_year,nil) -- convert to boolean if trim==nil and type(trim_year)=='string' then -- if "trim_year" not a simple True/False than it is range of dates -- for example '100-999' means to pad one and 2 digit years to be 4 digit long, while keeping 3 digit years as is local YMin, YMax = trim_year:match( '(%d+)-(%d+)' ) trim = (YMin~=nil and year>=tonumber(YMin) and year<=tonumber(YMax)) end if trim==true then datestr = trimYear(datestr, year, lang) -- in datestr replace long year with trimmed one end end -- append timezone if present if datenum[7] and (datecode == 'YMDHMS' or datecode == 'YMDHM') then -- use {{#time}} parser function to create timezone string, so that we use correct character set local sign = (datenum[7]<0) and '−' or '+' timeStamp = string.format("2000-01-01 %02i:%02i:00", math.abs(datenum[7]), datevec[8] or 0) local timezone = mw.language.new(lang):formatDate( 'H:i', timeStamp) -- same as using {{#time}} parser function datestr = string.format("%s %s%s", datestr, sign, timezone ) end -- html formating and tagging of date string if class and class ~= '' and datecode~='M' and datecode~='MD'then local DateHtmlTags = '<span style="white-space:nowrap"><time class="%s" datetime="%s">%s</time></span>' datestr = DateHtmlTags:format(class, timeStamp, datestr) end return datestr end --[[ ======================================================================================== Date This function is the core part of the ISOdate template. Usage: {{#invoke:DateI18n|Date|year=|month=|day=|hour=|minute=|second=|tzhour=|tzmin=|lang=en}} Parameters: * year, month, day, hour, minute, second: broken down date-time component strings * tzhour, tzmin: timezone offset from UTC, hours and minutes * lang: The language to display it in * case: Language format (genitive, etc.) for some languages * class: CSS class for the <time> node, use "" for no metadata at all ]] function p.Date(frame) local args = {} for name, value in pairs( frame.args ) do name = string.gsub( string.lower(name), ' ', '_') args[name] = value end return p._Date( { args.year, args.month, args.day, args.hour, args.minute, args.second, args.tzhour, args.tzmin }, args.lang, -- language args.case, -- allows to specify grammatical case for the month for languages that use them args.class or 'dtstart', -- allows to set the html class of the time node where the date is included. This is useful for microformats. args.trim_year or '100-999' -- by default pad one and 2 digit years to be 4 digit long, while keeping 3 digit years as is ) end return p tvygiry26r6040u8ipj0ym16hioyicd 803616 803615 2020-04-12T13:31:24Z commons>Jarekt 0 correction to the last edit 803616 Scribunto text/plain --[[ __ __ _ _ ____ _ ___ _ ___ | \/ | ___ __| |_ _| | ___ _| _ \ __ _| |_ ___|_ _/ |( _ ) _ __ | |\/| |/ _ \ / _` | | | | |/ _ (_) | | |/ _` | __/ _ \| || |/ _ \| '_ \ | | | | (_) | (_| | |_| | | __/_| |_| | (_| | || __/| || | (_) | | | | |_| |_|\___/ \__,_|\__,_|_|\___(_)____/ \__,_|\__\___|___|_|\___/|_| |_| This module is intended for processing of date strings. Please do not modify this code without applying the changes first at Module:Date/sandbox and testing at Module:Date/sandbox/testcases and Module talk:Date/sandbox/testcases. Authors and maintainers: * User:Parent5446 - original version of the function mimicking template:ISOdate * User:Jarekt - original version of the functions mimicking template:Date ]] require('Module:No globals') -- ================================================== -- === Internal functions =========================== -- ================================================== -- Function allowing for consistent treatment of boolean-like wikitext input. -- It works similarly to Module:Yesno local function yesno(val, default) if type(val) == 'boolean' then return val elseif type(val) == 'number' then if val==1 then return true elseif val==0 then return false end elseif type(val) == 'string' then val = mw.ustring.lower(val) -- put in lower case if val == 'no' or val == 'n' or val == 'false' or tonumber(val) == 0 then return false elseif val == 'yes' or val == 'y' or val == 'true' or tonumber(val) == 1 then return true end end return default end --------------------------------------------------------------------------------------- -- String replacement that ignores part of the string in "..." local function strReplace(String, old, new) if String:find('"') then local T={} for i, str in ipairs(mw.text.split( String, '"', true )) do if i%2==1 then str = str:gsub(old, new) end table.insert(T, str) end return table.concat(T,'"') else return String:gsub(old, new) end end --------------------------------------------------------------------------------------- -- process datevec -- INPUT: -- * datevec - Array of {year,month,day,hour,minute,second, tzhour, tzmin} containing broken -- down date-time component strings or numbers -- OUTPUT: -- * datecode - a code specifying content of the array where Y' is year, 'M' is month, -- 'D' is day, 'H' is hour, 'M' minute, 'S' is second. output has to be one of YMDHMS, YMDHM, YMD, YM, MD, Y -- * datenum - same array but holding only numbers or nuls local function parserDatevec(datevec) -- if month is not a number than check if it is a month name in project's language local month = datevec[2] if month and not tonumber(month) then datevec[2] = mw.getContentLanguage():formatDate( "n", month) end -- create datecode based on which variables are provided and check for out-of-bound values local maxval = {nil, 12, 31, 23, 59, 59, 23, 59} -- max values for year, month, ... local minval = {nil, 1, 0, 0, 0, 0, -23, 0} -- min values for year, month, ... local c = {'Y', 'M', 'D', 'H', 'M', 'S', '', ''} local datecode = '' -- a string signifying which combination of variables was provided local datenum = {} -- date-time encoded as a vector = [year, month, ... , second] for i = 1,8 do datenum[i] = tonumber(datevec[i]) if datenum[i] and (i==1 or (datenum[i]>=minval[i] and datenum[i]<=maxval[i])) then datecode = datecode .. c[i] end end return datecode, datenum end --------------------------------------------------------------------------------------- -- process datevec -- INPUT: -- * datecode - a code specifying content of the array where Y' is year, 'M' is month, -- 'D' is day, 'H' is hour, 'M' minute, 'S' is second. output has to be one of YMDHMS, YMDHM, YMD, YM, MD, Y -- * datenum - Array of {year,month,day,hour,minute,second, tzhour, tzmin} as numbers or nuls -- OUTPUT: -- * timeStamp - date string in the format taken by mw.language:formatDate lua function and {{#time}} perser function -- https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#mw.language:formatDate -- https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions#.23time -- * datecode - with possible corrections local function getTimestamp(datecode, datenum) -- create time stamp string (for example 2000-02-20 02:20:20) based on which variables were provided local timeStamp if datecode == 'YMDHMS' then timeStamp = string.format('%04i-%02i-%02i %02i:%02i:%02i', datenum[1], datenum[2], datenum[3], datenum[4], datenum[5], datenum[6] ) elseif datecode == 'YMDHM' then timeStamp = string.format('%04i-%02i-%02i %02i:%02i', datenum[1], datenum[2], datenum[3], datenum[4], datenum[5] ) elseif datecode:sub(1,3)=='YMD' then timeStamp = string.format('%04i-%02i-%02i', datenum[1], datenum[2], datenum[3] ) datecode = 'YMD' -- 'YMD', 'YMDHMS' and 'YMDHM' are the only supported format starting with 'YMD'. All others will be converted to 'YMD' elseif datecode == 'YM' then timeStamp = string.format('%04i-%02i', datenum[1], datenum[2] ) elseif datecode:sub(1,1)=='Y' then timeStamp = string.format('%04i', datenum[1] ) datecode = 'Y' elseif datecode == 'M' then timeStamp = string.format('%04i-%02i-%02i', 2000, datenum[2], 1 ) elseif datecode == 'MD' then timeStamp = string.format('%04i-%02i-%02i', 2000, datenum[2], datenum[3] ) else timeStamp = nil -- format not supported end return timeStamp, datecode end --------------------------------------------------------------------------------------- -- trim leading zeros in years prior to year 1000 -- INPUT: -- * datestr - translated date string -- * lang - language of translation -- OUTPUT: -- * datestr - updated date string local function trimYear(datestr, year, lang) local yearStr0, yearStr1, yearStr2, zeroStr yearStr0 = string.format('%04i', year ) -- 4 digit year in standard form "0123" yearStr1 = mw.language.new(lang):formatDate( 'Y', yearStr0) -- same as calling {{#time}} parser function --yearStr1 = mw.getCurrentFrame():callParserFunction( "#time", { 'Y', yearStr0, lang } ) -- translate to a language if yearStr0==yearStr1 then -- most of languages use standard form of year yearStr2 = tostring(year) else -- some languages use different characters for numbers yearStr2 = yearStr1 zeroStr = mw.ustring.sub(yearStr1,1,1) -- get "0" in whatever language for i=1,3 do -- trim leading zeros if mw.ustring.sub(yearStr2,1,1)==zeroStr then yearStr2 = mw.ustring.sub(yearStr2, 2, 5-i) else break end end end return string.gsub(datestr, yearStr1, yearStr2 ) -- in datestr replace long year with trimmed one end --------------------------------------------------------------------------------------- -- Look up proper format string to be passed to {{#time}} parser function -- INPUTS: -- * datecode: YMDHMS, YMDHM, YMD, YM, MD, Y, or M -- * day : Number between 1 and 31 (not needed for most languages) -- * lang : language -- OUTPUT: -- * dFormat : input to {{#time}} function local function getDateFormat(datecode, day, lang) local function parseFormat(dFormat, day) if dFormat:find('default') and #dFormat>10 then -- special (and messy) case of dFormat code depending on a day number -- then json contains a string with more json containing "default" field and 2 digit day keys -- if desired day is not in that json than use "default" case dFormat = dFormat:gsub('”','"') -- change fancy double quote to a straight one, used for json marking local D = mw.text.jsonDecode( dFormat ) --com = mw.dumpObject(D) day = string.format('d%02i',day) -- create day key dFormat = D[day] or D.default dFormat = dFormat:gsub("'", '"') -- change single quote to a double quote, used for {{#time}} marking end return dFormat end local T = {} local tab = mw.ext.data.get('DateI18n.tab', lang) for _, row in pairs(tab.data) do -- convert the output into a dictionary table local id, _, msg = unpack(row) T[id] = msg end local dFormat = T[datecode] if dFormat=='default' and (datecode=='YMDHMS' or datecode=='YMDHM') then -- for most languages adding hour:minute:second is done by adding ", HH:MM:SS to the -- day precission date, those languages are skipped in DateI18n.tab and default to -- English which stores word "default" dFormat = parseFormat(T['YMD'], day).. ', H:i' if datecode=='YMDHMS' then dFormat = dFormat .. ':s' end else dFormat = parseFormat(dFormat, day) end return dFormat end --------------------------------------------------------------------------------------- -- Look up proper format string to be passed to {{#time}} parser function -- INPUTS: -- * month : month number -- * case : gramatic case abbriviation, like "ins", "loc" -- * lang : language -- OUTPUT: -- * dFormat : input to {{#time}} function local function MonthCase(month, case, lang) local T = {{},{},{},{},{},{},{},{},{},{},{},{}} local tab = mw.ext.data.get('I18n/MonthCases.tab', lang) for _, row in pairs(tab.data) do local mth, cs, msg = unpack(row) T[mth][cs] = msg end return T[month][case] end -- ================================================== -- === External functions =========================== -- ================================================== local p = {} --[[ ======================================================================================== Date This function is the core part of the ISOdate template. Usage: local Date = require('Module:DateI18n')._Date local dateStr = Date({2020, 12, 30, 12, 20, 11}, lang) Parameters: * {year,month,day,hour,minute,second, tzhour, tzmin}: broken down date-time component strings or numbers tzhour, tzmin are timezone offsets from UTC, hours and minutes * lang: The language to display it in * case: Language format (genitive, etc.) for some languages * class: CSS class for the <time> node, use "" for no metadata at all ]] function p._Date(datevec, lang, case, class, trim_year) -- make sure inputs are in the right format if not lang or not mw.language.isValidCode( lang ) then lang = mw.getCurrentFrame():callParserFunction( "int", "lang" ) -- get user's chosen language end if lang == 'be-tarsk' then lang = 'be-x-old' end -- process datevec and extract timeStamp and datecode strings as well as numeric datenum array local datecode, datenum = parserDatevec(datevec) local year, month, day = datenum[1], datenum[2], datenum[3] local timeStamp, datecode = getTimestamp(datecode, datenum) if not timeStamp then -- something went wrong in parserDatevec return '' end -- Commons [[Data:DateI18n.tab]] page stores prefered formats for diferent -- languages and datecodes (specifying year-month-day or just year of month-day, etc) -- Look up country specific format input to {{#time}} function local dFormat = getDateFormat(datecode, day, lang) -- By default the gramatical case is not specified (case=='') allowing the format to be specified -- in [[Data:DateI18n.tab]]. You can overwrite the default grammatical case of the month by -- specifying "case" variable. This is needed mostly by Slavic languages to create more complex -- phrases as it is done in [[c:Module:Complex date]] case = case or '' if (lang=='qu' or lang=='qug') and (case=='nom') then -- Special case related to Quechua and Kichwa languages. The form in the I18n is -- Genitive case with suffix "pi" added to month names provided by {#time}} -- in Nominative case that "pi" should be removed -- see https://commons.wikimedia.org/wiki/Template_talk:Date#Quechua from 2014 dFormat = dFormat:gsub('F"pi"', 'F') elseif (case=='gen') then dFormat = strReplace(dFormat, "F", "xg") elseif (case=='nom') then dFormat = strReplace(dFormat, "xg", "F") elseif (case ~= '') then -- see is page [[Data:I18n/MonthCases.tab]] on Commons have name of the month -- in specific gramatic case in desired language. If we have it than replace -- "F" and xg" in dFormat local monthMsg = MonthCase(month, case, lang) if monthMsg and monthMsg ~= '' then -- make sure it exists dFormat = strReplace(dFormat, 'F', '"'..monthMsg..'"') -- replace default month with month name we already looked up dFormat = strReplace(dFormat, 'xg', '"'..monthMsg..'"') end end -- Translate the date using specified format -- See https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#mw.language:formatDate and -- https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions##time for explanation of the format local datestr = mw.language.new(lang):formatDate( dFormat, timeStamp) -- same as using {{#time}} parser function -- Special case related to Thai solar calendar: prior to 1940 new-year was at different -- time of year, so just year (datecode=='Y') is ambiguous and is replaced by "YYYY or YYYY" phrase if lang=='th' and datecode=='Y' and year<=1940 then datestr = string.format('%04i หรือ %04i', year+542, year+543 ) end -- If year<1000 than either keep the date padded to the length of 4 digits or trim it -- decide if the year will stay padded with zeros (for years in 0-999 range) if year and year<1000 then if type(trim_year)=='nil' then trim_year = '100-999' end local trim = yesno(trim_year,nil) -- convert to boolean if trim==nil and type(trim_year)=='string' then -- if "trim_year" not a simple True/False than it is range of dates -- for example '100-999' means to pad one and 2 digit years to be 4 digit long, while keeping 3 digit years as is local YMin, YMax = trim_year:match( '(%d+)-(%d+)' ) trim = (YMin~=nil and year>=tonumber(YMin) and year<=tonumber(YMax)) end if trim==true then datestr = trimYear(datestr, year, lang) -- in datestr replace long year with trimmed one end end -- append timezone if present if datenum[7] and (datecode == 'YMDHMS' or datecode == 'YMDHM') then -- use {{#time}} parser function to create timezone string, so that we use correct character set local sign = (datenum[7]<0) and '−' or '+' timeStamp = string.format("2000-01-01 %02i:%02i:00", math.abs(datenum[7]), datenum[8] or 0) local timezone = mw.language.new(lang):formatDate( 'H:i', timeStamp) -- same as using {{#time}} parser function datestr = string.format("%s %s%s", datestr, sign, timezone ) end -- html formating and tagging of date string if class and class ~= '' and datecode~='M' and datecode~='MD'then local DateHtmlTags = '<span style="white-space:nowrap"><time class="%s" datetime="%s">%s</time></span>' datestr = DateHtmlTags:format(class, timeStamp, datestr) end return datestr end --[[ ======================================================================================== Date This function is the core part of the ISOdate template. Usage: {{#invoke:DateI18n|Date|year=|month=|day=|hour=|minute=|second=|tzhour=|tzmin=|lang=en}} Parameters: * year, month, day, hour, minute, second: broken down date-time component strings * tzhour, tzmin: timezone offset from UTC, hours and minutes * lang: The language to display it in * case: Language format (genitive, etc.) for some languages * class: CSS class for the <time> node, use "" for no metadata at all ]] function p.Date(frame) local args = {} for name, value in pairs( frame.args ) do name = string.gsub( string.lower(name), ' ', '_') args[name] = value end return p._Date( { args.year, args.month, args.day, args.hour, args.minute, args.second, args.tzhour, args.tzmin }, args.lang, -- language args.case, -- allows to specify grammatical case for the month for languages that use them args.class or 'dtstart', -- allows to set the html class of the time node where the date is included. This is useful for microformats. args.trim_year or '100-999' -- by default pad one and 2 digit years to be 4 digit long, while keeping 3 digit years as is ) end return p k5zdhfc2luhh5kdznmwbvpw0doztyr2 803617 803616 2020-04-12T14:51:53Z commons>Jarekt 0 fix issue where "april" was added to the year only date 803617 Scribunto text/plain --[[ __ __ _ _ ____ _ ___ _ ___ | \/ | ___ __| |_ _| | ___ _| _ \ __ _| |_ ___|_ _/ |( _ ) _ __ | |\/| |/ _ \ / _` | | | | |/ _ (_) | | |/ _` | __/ _ \| || |/ _ \| '_ \ | | | | (_) | (_| | |_| | | __/_| |_| | (_| | || __/| || | (_) | | | | |_| |_|\___/ \__,_|\__,_|_|\___(_)____/ \__,_|\__\___|___|_|\___/|_| |_| This module is intended for processing of date strings. Please do not modify this code without applying the changes first at Module:Date/sandbox and testing at Module:Date/sandbox/testcases and Module talk:Date/sandbox/testcases. Authors and maintainers: * User:Parent5446 - original version of the function mimicking template:ISOdate * User:Jarekt - original version of the functions mimicking template:Date ]] require('Module:No globals') -- ================================================== -- === Internal functions =========================== -- ================================================== -- Function allowing for consistent treatment of boolean-like wikitext input. -- It works similarly to Module:Yesno local function yesno(val, default) if type(val) == 'boolean' then return val elseif type(val) == 'number' then if val==1 then return true elseif val==0 then return false end elseif type(val) == 'string' then val = mw.ustring.lower(val) -- put in lower case if val == 'no' or val == 'n' or val == 'false' or tonumber(val) == 0 then return false elseif val == 'yes' or val == 'y' or val == 'true' or tonumber(val) == 1 then return true end end return default end --------------------------------------------------------------------------------------- -- String replacement that ignores part of the string in "..." local function strReplace(String, old, new) if String:find('"') then local T={} for i, str in ipairs(mw.text.split( String, '"', true )) do if i%2==1 then str = str:gsub(old, new) end table.insert(T, str) end return table.concat(T,'"') else return String:gsub(old, new) end end --------------------------------------------------------------------------------------- -- process datevec -- INPUT: -- * datevec - Array of {year,month,day,hour,minute,second, tzhour, tzmin} containing broken -- down date-time component strings or numbers -- OUTPUT: -- * datecode - a code specifying content of the array where Y' is year, 'M' is month, -- 'D' is day, 'H' is hour, 'M' minute, 'S' is second. output has to be one of YMDHMS, YMDHM, YMD, YM, MD, Y -- * datenum - same array but holding only numbers or nuls local function parserDatevec(datevec) -- if month is not a number than check if it is a month name in project's language local month = datevec[2] if month and month~='' and not tonumber(month) then datevec[2] = mw.getContentLanguage():formatDate( "n", month) end -- create datecode based on which variables are provided and check for out-of-bound values local maxval = {nil, 12, 31, 23, 59, 59, 23, 59} -- max values for year, month, ... local minval = {nil, 1, 1, 0, 0, 0, -23, 0} -- min values for year, month, ... local c = {'Y', 'M', 'D', 'H', 'M', 'S', '', ''} local datecode = '' -- a string signifying which combination of variables was provided local datenum = {} -- date-time encoded as a vector = [year, month, ... , second] for i = 1,8 do datenum[i] = tonumber(datevec[i]) if datenum[i] and (i==1 or (datenum[i]>=minval[i] and datenum[i]<=maxval[i])) then datecode = datecode .. c[i] end end return datecode, datenum end --------------------------------------------------------------------------------------- -- process datevec -- INPUT: -- * datecode - a code specifying content of the array where Y' is year, 'M' is month, -- 'D' is day, 'H' is hour, 'M' minute, 'S' is second. output has to be one of YMDHMS, YMDHM, YMD, YM, MD, Y -- * datenum - Array of {year,month,day,hour,minute,second, tzhour, tzmin} as numbers or nuls -- OUTPUT: -- * timeStamp - date string in the format taken by mw.language:formatDate lua function and {{#time}} perser function -- https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#mw.language:formatDate -- https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions#.23time -- * datecode - with possible corrections local function getTimestamp(datecode, datenum) -- create time stamp string (for example 2000-02-20 02:20:20) based on which variables were provided local timeStamp if datecode == 'YMDHMS' then timeStamp = string.format('%04i-%02i-%02i %02i:%02i:%02i', datenum[1], datenum[2], datenum[3], datenum[4], datenum[5], datenum[6] ) elseif datecode == 'YMDHM' then timeStamp = string.format('%04i-%02i-%02i %02i:%02i', datenum[1], datenum[2], datenum[3], datenum[4], datenum[5] ) elseif datecode:sub(1,3)=='YMD' then timeStamp = string.format('%04i-%02i-%02i', datenum[1], datenum[2], datenum[3] ) datecode = 'YMD' -- 'YMD', 'YMDHMS' and 'YMDHM' are the only supported format starting with 'YMD'. All others will be converted to 'YMD' elseif datecode == 'YM' then timeStamp = string.format('%04i-%02i', datenum[1], datenum[2] ) elseif datecode:sub(1,1)=='Y' then timeStamp = string.format('%04i', datenum[1] ) datecode = 'Y' elseif datecode == 'M' then timeStamp = string.format('%04i-%02i-%02i', 2000, datenum[2], 1 ) elseif datecode == 'MD' then timeStamp = string.format('%04i-%02i-%02i', 2000, datenum[2], datenum[3] ) else timeStamp = nil -- format not supported end return timeStamp, datecode end --------------------------------------------------------------------------------------- -- trim leading zeros in years prior to year 1000 -- INPUT: -- * datestr - translated date string -- * lang - language of translation -- OUTPUT: -- * datestr - updated date string local function trimYear(datestr, year, lang) local yearStr0, yearStr1, yearStr2, zeroStr yearStr0 = string.format('%04i', year ) -- 4 digit year in standard form "0123" yearStr1 = mw.language.new(lang):formatDate( 'Y', yearStr0) -- same as calling {{#time}} parser function --yearStr1 = mw.getCurrentFrame():callParserFunction( "#time", { 'Y', yearStr0, lang } ) -- translate to a language if yearStr0==yearStr1 then -- most of languages use standard form of year yearStr2 = tostring(year) else -- some languages use different characters for numbers yearStr2 = yearStr1 zeroStr = mw.ustring.sub(yearStr1,1,1) -- get "0" in whatever language for i=1,3 do -- trim leading zeros if mw.ustring.sub(yearStr2,1,1)==zeroStr then yearStr2 = mw.ustring.sub(yearStr2, 2, 5-i) else break end end end return string.gsub(datestr, yearStr1, yearStr2 ) -- in datestr replace long year with trimmed one end --------------------------------------------------------------------------------------- -- Look up proper format string to be passed to {{#time}} parser function -- INPUTS: -- * datecode: YMDHMS, YMDHM, YMD, YM, MD, Y, or M -- * day : Number between 1 and 31 (not needed for most languages) -- * lang : language -- OUTPUT: -- * dFormat : input to {{#time}} function local function getDateFormat(datecode, day, lang) local function parseFormat(dFormat, day) if dFormat:find('default') and #dFormat>10 then -- special (and messy) case of dFormat code depending on a day number -- then json contains a string with more json containing "default" field and 2 digit day keys -- if desired day is not in that json than use "default" case dFormat = dFormat:gsub('”','"') -- change fancy double quote to a straight one, used for json marking local D = mw.text.jsonDecode( dFormat ) --com = mw.dumpObject(D) day = string.format('d%02i',day) -- create day key dFormat = D[day] or D.default dFormat = dFormat:gsub("'", '"') -- change single quote to a double quote, used for {{#time}} marking end return dFormat end local T = {} local tab = mw.ext.data.get('DateI18n.tab', lang) for _, row in pairs(tab.data) do -- convert the output into a dictionary table local id, _, msg = unpack(row) T[id] = msg end local dFormat = T[datecode] if dFormat=='default' and (datecode=='YMDHMS' or datecode=='YMDHM') then -- for most languages adding hour:minute:second is done by adding ", HH:MM:SS to the -- day precission date, those languages are skipped in DateI18n.tab and default to -- English which stores word "default" dFormat = parseFormat(T['YMD'], day).. ', H:i' if datecode=='YMDHMS' then dFormat = dFormat .. ':s' end else dFormat = parseFormat(dFormat, day) end return dFormat end --------------------------------------------------------------------------------------- -- Look up proper format string to be passed to {{#time}} parser function -- INPUTS: -- * month : month number -- * case : gramatic case abbriviation, like "ins", "loc" -- * lang : language -- OUTPUT: -- * dFormat : input to {{#time}} function local function MonthCase(month, case, lang) local T = {{},{},{},{},{},{},{},{},{},{},{},{}} local tab = mw.ext.data.get('I18n/MonthCases.tab', lang) for _, row in pairs(tab.data) do local mth, cs, msg = unpack(row) T[mth][cs] = msg end return T[month][case] end -- ================================================== -- === External functions =========================== -- ================================================== local p = {} --[[ ======================================================================================== Date This function is the core part of the ISOdate template. Usage: local Date = require('Module:DateI18n')._Date local dateStr = Date({2020, 12, 30, 12, 20, 11}, lang) Parameters: * {year,month,day,hour,minute,second, tzhour, tzmin}: broken down date-time component strings or numbers tzhour, tzmin are timezone offsets from UTC, hours and minutes * lang: The language to display it in * case: Language format (genitive, etc.) for some languages * class: CSS class for the <time> node, use "" for no metadata at all ]] function p._Date(datevec, lang, case, class, trim_year) -- make sure inputs are in the right format if not lang or not mw.language.isValidCode( lang ) then lang = mw.getCurrentFrame():callParserFunction( "int", "lang" ) -- get user's chosen language end if lang == 'be-tarsk' then lang = 'be-x-old' end -- process datevec and extract timeStamp and datecode strings as well as numeric datenum array local datecode, datenum = parserDatevec(datevec) local year, month, day = datenum[1], datenum[2], datenum[3] local timeStamp, datecode = getTimestamp(datecode, datenum) if not timeStamp then -- something went wrong in parserDatevec return '' end -- Commons [[Data:DateI18n.tab]] page stores prefered formats for diferent -- languages and datecodes (specifying year-month-day or just year of month-day, etc) -- Look up country specific format input to {{#time}} function local dFormat = getDateFormat(datecode, day, lang) -- By default the gramatical case is not specified (case=='') allowing the format to be specified -- in [[Data:DateI18n.tab]]. You can overwrite the default grammatical case of the month by -- specifying "case" variable. This is needed mostly by Slavic languages to create more complex -- phrases as it is done in [[c:Module:Complex date]] case = case or '' if (lang=='qu' or lang=='qug') and (case=='nom') then -- Special case related to Quechua and Kichwa languages. The form in the I18n is -- Genitive case with suffix "pi" added to month names provided by {#time}} -- in Nominative case that "pi" should be removed -- see https://commons.wikimedia.org/wiki/Template_talk:Date#Quechua from 2014 dFormat = dFormat:gsub('F"pi"', 'F') elseif (case=='gen') then dFormat = strReplace(dFormat, "F", "xg") elseif (case=='nom') then dFormat = strReplace(dFormat, "xg", "F") elseif (case ~= '') then -- see is page [[Data:I18n/MonthCases.tab]] on Commons have name of the month -- in specific gramatic case in desired language. If we have it than replace -- "F" and xg" in dFormat local monthMsg = MonthCase(month, case, lang) if monthMsg and monthMsg ~= '' then -- make sure it exists dFormat = strReplace(dFormat, 'F', '"'..monthMsg..'"') -- replace default month with month name we already looked up dFormat = strReplace(dFormat, 'xg', '"'..monthMsg..'"') end end -- Translate the date using specified format -- See https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#mw.language:formatDate and -- https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions##time for explanation of the format local datestr = mw.language.new(lang):formatDate( dFormat, timeStamp) -- same as using {{#time}} parser function -- Special case related to Thai solar calendar: prior to 1940 new-year was at different -- time of year, so just year (datecode=='Y') is ambiguous and is replaced by "YYYY or YYYY" phrase if lang=='th' and datecode=='Y' and year<=1940 then datestr = string.format('%04i หรือ %04i', year+542, year+543 ) end -- If year<1000 than either keep the date padded to the length of 4 digits or trim it -- decide if the year will stay padded with zeros (for years in 0-999 range) if year and year<1000 then if type(trim_year)=='nil' then trim_year = '100-999' end local trim = yesno(trim_year,nil) -- convert to boolean if trim==nil and type(trim_year)=='string' then -- if "trim_year" not a simple True/False than it is range of dates -- for example '100-999' means to pad one and 2 digit years to be 4 digit long, while keeping 3 digit years as is local YMin, YMax = trim_year:match( '(%d+)-(%d+)' ) trim = (YMin~=nil and year>=tonumber(YMin) and year<=tonumber(YMax)) end if trim==true then datestr = trimYear(datestr, year, lang) -- in datestr replace long year with trimmed one end end -- append timezone if present if datenum[7] and (datecode == 'YMDHMS' or datecode == 'YMDHM') then -- use {{#time}} parser function to create timezone string, so that we use correct character set local sign = (datenum[7]<0) and '−' or '+' timeStamp = string.format("2000-01-01 %02i:%02i:00", math.abs(datenum[7]), datenum[8] or 0) local timezone = mw.language.new(lang):formatDate( 'H:i', timeStamp) -- same as using {{#time}} parser function datestr = string.format("%s %s%s", datestr, sign, timezone ) end -- html formating and tagging of date string if class and class ~= '' and datecode~='M' and datecode~='MD'then local DateHtmlTags = '<span style="white-space:nowrap"><time class="%s" datetime="%s">%s</time></span>' datestr = DateHtmlTags:format(class, timeStamp, datestr) end return datestr end --[[ ======================================================================================== Date This function is the core part of the ISOdate template. Usage: {{#invoke:DateI18n|Date|year=|month=|day=|hour=|minute=|second=|tzhour=|tzmin=|lang=en}} Parameters: * year, month, day, hour, minute, second: broken down date-time component strings * tzhour, tzmin: timezone offset from UTC, hours and minutes * lang: The language to display it in * case: Language format (genitive, etc.) for some languages * class: CSS class for the <time> node, use "" for no metadata at all ]] function p.Date(frame) local args = {} for name, value in pairs( frame.args ) do name = string.gsub( string.lower(name), ' ', '_') args[name] = value end return p._Date( { args.year, args.month, args.day, args.hour, args.minute, args.second, args.tzhour, args.tzmin }, args.lang, -- language args.case, -- allows to specify grammatical case for the month for languages that use them args.class or 'dtstart', -- allows to set the html class of the time node where the date is included. This is useful for microformats. args.trim_year or '100-999' -- by default pad one and 2 digit years to be 4 digit long, while keeping 3 digit years as is ) end return p ber4mpu06s9vonpspt2y1ttwq6gczp6 803618 803617 2020-04-12T23:27:35Z commons>Jarekt 0 Protected "[[Module:DateI18n]]": [[Commons:Protection policy|High traffic page]] ([Edit=Allow only template editors and administrators] (indefinite) [Move=Allow only template editors and administrators] (indefinite)) 803617 Scribunto text/plain --[[ __ __ _ _ ____ _ ___ _ ___ | \/ | ___ __| |_ _| | ___ _| _ \ __ _| |_ ___|_ _/ |( _ ) _ __ | |\/| |/ _ \ / _` | | | | |/ _ (_) | | |/ _` | __/ _ \| || |/ _ \| '_ \ | | | | (_) | (_| | |_| | | __/_| |_| | (_| | || __/| || | (_) | | | | |_| |_|\___/ \__,_|\__,_|_|\___(_)____/ \__,_|\__\___|___|_|\___/|_| |_| This module is intended for processing of date strings. Please do not modify this code without applying the changes first at Module:Date/sandbox and testing at Module:Date/sandbox/testcases and Module talk:Date/sandbox/testcases. Authors and maintainers: * User:Parent5446 - original version of the function mimicking template:ISOdate * User:Jarekt - original version of the functions mimicking template:Date ]] require('Module:No globals') -- ================================================== -- === Internal functions =========================== -- ================================================== -- Function allowing for consistent treatment of boolean-like wikitext input. -- It works similarly to Module:Yesno local function yesno(val, default) if type(val) == 'boolean' then return val elseif type(val) == 'number' then if val==1 then return true elseif val==0 then return false end elseif type(val) == 'string' then val = mw.ustring.lower(val) -- put in lower case if val == 'no' or val == 'n' or val == 'false' or tonumber(val) == 0 then return false elseif val == 'yes' or val == 'y' or val == 'true' or tonumber(val) == 1 then return true end end return default end --------------------------------------------------------------------------------------- -- String replacement that ignores part of the string in "..." local function strReplace(String, old, new) if String:find('"') then local T={} for i, str in ipairs(mw.text.split( String, '"', true )) do if i%2==1 then str = str:gsub(old, new) end table.insert(T, str) end return table.concat(T,'"') else return String:gsub(old, new) end end --------------------------------------------------------------------------------------- -- process datevec -- INPUT: -- * datevec - Array of {year,month,day,hour,minute,second, tzhour, tzmin} containing broken -- down date-time component strings or numbers -- OUTPUT: -- * datecode - a code specifying content of the array where Y' is year, 'M' is month, -- 'D' is day, 'H' is hour, 'M' minute, 'S' is second. output has to be one of YMDHMS, YMDHM, YMD, YM, MD, Y -- * datenum - same array but holding only numbers or nuls local function parserDatevec(datevec) -- if month is not a number than check if it is a month name in project's language local month = datevec[2] if month and month~='' and not tonumber(month) then datevec[2] = mw.getContentLanguage():formatDate( "n", month) end -- create datecode based on which variables are provided and check for out-of-bound values local maxval = {nil, 12, 31, 23, 59, 59, 23, 59} -- max values for year, month, ... local minval = {nil, 1, 1, 0, 0, 0, -23, 0} -- min values for year, month, ... local c = {'Y', 'M', 'D', 'H', 'M', 'S', '', ''} local datecode = '' -- a string signifying which combination of variables was provided local datenum = {} -- date-time encoded as a vector = [year, month, ... , second] for i = 1,8 do datenum[i] = tonumber(datevec[i]) if datenum[i] and (i==1 or (datenum[i]>=minval[i] and datenum[i]<=maxval[i])) then datecode = datecode .. c[i] end end return datecode, datenum end --------------------------------------------------------------------------------------- -- process datevec -- INPUT: -- * datecode - a code specifying content of the array where Y' is year, 'M' is month, -- 'D' is day, 'H' is hour, 'M' minute, 'S' is second. output has to be one of YMDHMS, YMDHM, YMD, YM, MD, Y -- * datenum - Array of {year,month,day,hour,minute,second, tzhour, tzmin} as numbers or nuls -- OUTPUT: -- * timeStamp - date string in the format taken by mw.language:formatDate lua function and {{#time}} perser function -- https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#mw.language:formatDate -- https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions#.23time -- * datecode - with possible corrections local function getTimestamp(datecode, datenum) -- create time stamp string (for example 2000-02-20 02:20:20) based on which variables were provided local timeStamp if datecode == 'YMDHMS' then timeStamp = string.format('%04i-%02i-%02i %02i:%02i:%02i', datenum[1], datenum[2], datenum[3], datenum[4], datenum[5], datenum[6] ) elseif datecode == 'YMDHM' then timeStamp = string.format('%04i-%02i-%02i %02i:%02i', datenum[1], datenum[2], datenum[3], datenum[4], datenum[5] ) elseif datecode:sub(1,3)=='YMD' then timeStamp = string.format('%04i-%02i-%02i', datenum[1], datenum[2], datenum[3] ) datecode = 'YMD' -- 'YMD', 'YMDHMS' and 'YMDHM' are the only supported format starting with 'YMD'. All others will be converted to 'YMD' elseif datecode == 'YM' then timeStamp = string.format('%04i-%02i', datenum[1], datenum[2] ) elseif datecode:sub(1,1)=='Y' then timeStamp = string.format('%04i', datenum[1] ) datecode = 'Y' elseif datecode == 'M' then timeStamp = string.format('%04i-%02i-%02i', 2000, datenum[2], 1 ) elseif datecode == 'MD' then timeStamp = string.format('%04i-%02i-%02i', 2000, datenum[2], datenum[3] ) else timeStamp = nil -- format not supported end return timeStamp, datecode end --------------------------------------------------------------------------------------- -- trim leading zeros in years prior to year 1000 -- INPUT: -- * datestr - translated date string -- * lang - language of translation -- OUTPUT: -- * datestr - updated date string local function trimYear(datestr, year, lang) local yearStr0, yearStr1, yearStr2, zeroStr yearStr0 = string.format('%04i', year ) -- 4 digit year in standard form "0123" yearStr1 = mw.language.new(lang):formatDate( 'Y', yearStr0) -- same as calling {{#time}} parser function --yearStr1 = mw.getCurrentFrame():callParserFunction( "#time", { 'Y', yearStr0, lang } ) -- translate to a language if yearStr0==yearStr1 then -- most of languages use standard form of year yearStr2 = tostring(year) else -- some languages use different characters for numbers yearStr2 = yearStr1 zeroStr = mw.ustring.sub(yearStr1,1,1) -- get "0" in whatever language for i=1,3 do -- trim leading zeros if mw.ustring.sub(yearStr2,1,1)==zeroStr then yearStr2 = mw.ustring.sub(yearStr2, 2, 5-i) else break end end end return string.gsub(datestr, yearStr1, yearStr2 ) -- in datestr replace long year with trimmed one end --------------------------------------------------------------------------------------- -- Look up proper format string to be passed to {{#time}} parser function -- INPUTS: -- * datecode: YMDHMS, YMDHM, YMD, YM, MD, Y, or M -- * day : Number between 1 and 31 (not needed for most languages) -- * lang : language -- OUTPUT: -- * dFormat : input to {{#time}} function local function getDateFormat(datecode, day, lang) local function parseFormat(dFormat, day) if dFormat:find('default') and #dFormat>10 then -- special (and messy) case of dFormat code depending on a day number -- then json contains a string with more json containing "default" field and 2 digit day keys -- if desired day is not in that json than use "default" case dFormat = dFormat:gsub('”','"') -- change fancy double quote to a straight one, used for json marking local D = mw.text.jsonDecode( dFormat ) --com = mw.dumpObject(D) day = string.format('d%02i',day) -- create day key dFormat = D[day] or D.default dFormat = dFormat:gsub("'", '"') -- change single quote to a double quote, used for {{#time}} marking end return dFormat end local T = {} local tab = mw.ext.data.get('DateI18n.tab', lang) for _, row in pairs(tab.data) do -- convert the output into a dictionary table local id, _, msg = unpack(row) T[id] = msg end local dFormat = T[datecode] if dFormat=='default' and (datecode=='YMDHMS' or datecode=='YMDHM') then -- for most languages adding hour:minute:second is done by adding ", HH:MM:SS to the -- day precission date, those languages are skipped in DateI18n.tab and default to -- English which stores word "default" dFormat = parseFormat(T['YMD'], day).. ', H:i' if datecode=='YMDHMS' then dFormat = dFormat .. ':s' end else dFormat = parseFormat(dFormat, day) end return dFormat end --------------------------------------------------------------------------------------- -- Look up proper format string to be passed to {{#time}} parser function -- INPUTS: -- * month : month number -- * case : gramatic case abbriviation, like "ins", "loc" -- * lang : language -- OUTPUT: -- * dFormat : input to {{#time}} function local function MonthCase(month, case, lang) local T = {{},{},{},{},{},{},{},{},{},{},{},{}} local tab = mw.ext.data.get('I18n/MonthCases.tab', lang) for _, row in pairs(tab.data) do local mth, cs, msg = unpack(row) T[mth][cs] = msg end return T[month][case] end -- ================================================== -- === External functions =========================== -- ================================================== local p = {} --[[ ======================================================================================== Date This function is the core part of the ISOdate template. Usage: local Date = require('Module:DateI18n')._Date local dateStr = Date({2020, 12, 30, 12, 20, 11}, lang) Parameters: * {year,month,day,hour,minute,second, tzhour, tzmin}: broken down date-time component strings or numbers tzhour, tzmin are timezone offsets from UTC, hours and minutes * lang: The language to display it in * case: Language format (genitive, etc.) for some languages * class: CSS class for the <time> node, use "" for no metadata at all ]] function p._Date(datevec, lang, case, class, trim_year) -- make sure inputs are in the right format if not lang or not mw.language.isValidCode( lang ) then lang = mw.getCurrentFrame():callParserFunction( "int", "lang" ) -- get user's chosen language end if lang == 'be-tarsk' then lang = 'be-x-old' end -- process datevec and extract timeStamp and datecode strings as well as numeric datenum array local datecode, datenum = parserDatevec(datevec) local year, month, day = datenum[1], datenum[2], datenum[3] local timeStamp, datecode = getTimestamp(datecode, datenum) if not timeStamp then -- something went wrong in parserDatevec return '' end -- Commons [[Data:DateI18n.tab]] page stores prefered formats for diferent -- languages and datecodes (specifying year-month-day or just year of month-day, etc) -- Look up country specific format input to {{#time}} function local dFormat = getDateFormat(datecode, day, lang) -- By default the gramatical case is not specified (case=='') allowing the format to be specified -- in [[Data:DateI18n.tab]]. You can overwrite the default grammatical case of the month by -- specifying "case" variable. This is needed mostly by Slavic languages to create more complex -- phrases as it is done in [[c:Module:Complex date]] case = case or '' if (lang=='qu' or lang=='qug') and (case=='nom') then -- Special case related to Quechua and Kichwa languages. The form in the I18n is -- Genitive case with suffix "pi" added to month names provided by {#time}} -- in Nominative case that "pi" should be removed -- see https://commons.wikimedia.org/wiki/Template_talk:Date#Quechua from 2014 dFormat = dFormat:gsub('F"pi"', 'F') elseif (case=='gen') then dFormat = strReplace(dFormat, "F", "xg") elseif (case=='nom') then dFormat = strReplace(dFormat, "xg", "F") elseif (case ~= '') then -- see is page [[Data:I18n/MonthCases.tab]] on Commons have name of the month -- in specific gramatic case in desired language. If we have it than replace -- "F" and xg" in dFormat local monthMsg = MonthCase(month, case, lang) if monthMsg and monthMsg ~= '' then -- make sure it exists dFormat = strReplace(dFormat, 'F', '"'..monthMsg..'"') -- replace default month with month name we already looked up dFormat = strReplace(dFormat, 'xg', '"'..monthMsg..'"') end end -- Translate the date using specified format -- See https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#mw.language:formatDate and -- https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions##time for explanation of the format local datestr = mw.language.new(lang):formatDate( dFormat, timeStamp) -- same as using {{#time}} parser function -- Special case related to Thai solar calendar: prior to 1940 new-year was at different -- time of year, so just year (datecode=='Y') is ambiguous and is replaced by "YYYY or YYYY" phrase if lang=='th' and datecode=='Y' and year<=1940 then datestr = string.format('%04i หรือ %04i', year+542, year+543 ) end -- If year<1000 than either keep the date padded to the length of 4 digits or trim it -- decide if the year will stay padded with zeros (for years in 0-999 range) if year and year<1000 then if type(trim_year)=='nil' then trim_year = '100-999' end local trim = yesno(trim_year,nil) -- convert to boolean if trim==nil and type(trim_year)=='string' then -- if "trim_year" not a simple True/False than it is range of dates -- for example '100-999' means to pad one and 2 digit years to be 4 digit long, while keeping 3 digit years as is local YMin, YMax = trim_year:match( '(%d+)-(%d+)' ) trim = (YMin~=nil and year>=tonumber(YMin) and year<=tonumber(YMax)) end if trim==true then datestr = trimYear(datestr, year, lang) -- in datestr replace long year with trimmed one end end -- append timezone if present if datenum[7] and (datecode == 'YMDHMS' or datecode == 'YMDHM') then -- use {{#time}} parser function to create timezone string, so that we use correct character set local sign = (datenum[7]<0) and '−' or '+' timeStamp = string.format("2000-01-01 %02i:%02i:00", math.abs(datenum[7]), datenum[8] or 0) local timezone = mw.language.new(lang):formatDate( 'H:i', timeStamp) -- same as using {{#time}} parser function datestr = string.format("%s %s%s", datestr, sign, timezone ) end -- html formating and tagging of date string if class and class ~= '' and datecode~='M' and datecode~='MD'then local DateHtmlTags = '<span style="white-space:nowrap"><time class="%s" datetime="%s">%s</time></span>' datestr = DateHtmlTags:format(class, timeStamp, datestr) end return datestr end --[[ ======================================================================================== Date This function is the core part of the ISOdate template. Usage: {{#invoke:DateI18n|Date|year=|month=|day=|hour=|minute=|second=|tzhour=|tzmin=|lang=en}} Parameters: * year, month, day, hour, minute, second: broken down date-time component strings * tzhour, tzmin: timezone offset from UTC, hours and minutes * lang: The language to display it in * case: Language format (genitive, etc.) for some languages * class: CSS class for the <time> node, use "" for no metadata at all ]] function p.Date(frame) local args = {} for name, value in pairs( frame.args ) do name = string.gsub( string.lower(name), ' ', '_') args[name] = value end return p._Date( { args.year, args.month, args.day, args.hour, args.minute, args.second, args.tzhour, args.tzmin }, args.lang, -- language args.case, -- allows to specify grammatical case for the month for languages that use them args.class or 'dtstart', -- allows to set the html class of the time node where the date is included. This is useful for microformats. args.trim_year or '100-999' -- by default pad one and 2 digit years to be 4 digit long, while keeping 3 digit years as is ) end return p ber4mpu06s9vonpspt2y1ttwq6gczp6 803619 803618 2020-04-30T20:50:47Z en>RexxS 0 6 revisions imported from [[:commons:Module:DateI18n]]: updating to latest version 803617 Scribunto text/plain --[[ __ __ _ _ ____ _ ___ _ ___ | \/ | ___ __| |_ _| | ___ _| _ \ __ _| |_ ___|_ _/ |( _ ) _ __ | |\/| |/ _ \ / _` | | | | |/ _ (_) | | |/ _` | __/ _ \| || |/ _ \| '_ \ | | | | (_) | (_| | |_| | | __/_| |_| | (_| | || __/| || | (_) | | | | |_| |_|\___/ \__,_|\__,_|_|\___(_)____/ \__,_|\__\___|___|_|\___/|_| |_| This module is intended for processing of date strings. Please do not modify this code without applying the changes first at Module:Date/sandbox and testing at Module:Date/sandbox/testcases and Module talk:Date/sandbox/testcases. Authors and maintainers: * User:Parent5446 - original version of the function mimicking template:ISOdate * User:Jarekt - original version of the functions mimicking template:Date ]] require('Module:No globals') -- ================================================== -- === Internal functions =========================== -- ================================================== -- Function allowing for consistent treatment of boolean-like wikitext input. -- It works similarly to Module:Yesno local function yesno(val, default) if type(val) == 'boolean' then return val elseif type(val) == 'number' then if val==1 then return true elseif val==0 then return false end elseif type(val) == 'string' then val = mw.ustring.lower(val) -- put in lower case if val == 'no' or val == 'n' or val == 'false' or tonumber(val) == 0 then return false elseif val == 'yes' or val == 'y' or val == 'true' or tonumber(val) == 1 then return true end end return default end --------------------------------------------------------------------------------------- -- String replacement that ignores part of the string in "..." local function strReplace(String, old, new) if String:find('"') then local T={} for i, str in ipairs(mw.text.split( String, '"', true )) do if i%2==1 then str = str:gsub(old, new) end table.insert(T, str) end return table.concat(T,'"') else return String:gsub(old, new) end end --------------------------------------------------------------------------------------- -- process datevec -- INPUT: -- * datevec - Array of {year,month,day,hour,minute,second, tzhour, tzmin} containing broken -- down date-time component strings or numbers -- OUTPUT: -- * datecode - a code specifying content of the array where Y' is year, 'M' is month, -- 'D' is day, 'H' is hour, 'M' minute, 'S' is second. output has to be one of YMDHMS, YMDHM, YMD, YM, MD, Y -- * datenum - same array but holding only numbers or nuls local function parserDatevec(datevec) -- if month is not a number than check if it is a month name in project's language local month = datevec[2] if month and month~='' and not tonumber(month) then datevec[2] = mw.getContentLanguage():formatDate( "n", month) end -- create datecode based on which variables are provided and check for out-of-bound values local maxval = {nil, 12, 31, 23, 59, 59, 23, 59} -- max values for year, month, ... local minval = {nil, 1, 1, 0, 0, 0, -23, 0} -- min values for year, month, ... local c = {'Y', 'M', 'D', 'H', 'M', 'S', '', ''} local datecode = '' -- a string signifying which combination of variables was provided local datenum = {} -- date-time encoded as a vector = [year, month, ... , second] for i = 1,8 do datenum[i] = tonumber(datevec[i]) if datenum[i] and (i==1 or (datenum[i]>=minval[i] and datenum[i]<=maxval[i])) then datecode = datecode .. c[i] end end return datecode, datenum end --------------------------------------------------------------------------------------- -- process datevec -- INPUT: -- * datecode - a code specifying content of the array where Y' is year, 'M' is month, -- 'D' is day, 'H' is hour, 'M' minute, 'S' is second. output has to be one of YMDHMS, YMDHM, YMD, YM, MD, Y -- * datenum - Array of {year,month,day,hour,minute,second, tzhour, tzmin} as numbers or nuls -- OUTPUT: -- * timeStamp - date string in the format taken by mw.language:formatDate lua function and {{#time}} perser function -- https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#mw.language:formatDate -- https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions#.23time -- * datecode - with possible corrections local function getTimestamp(datecode, datenum) -- create time stamp string (for example 2000-02-20 02:20:20) based on which variables were provided local timeStamp if datecode == 'YMDHMS' then timeStamp = string.format('%04i-%02i-%02i %02i:%02i:%02i', datenum[1], datenum[2], datenum[3], datenum[4], datenum[5], datenum[6] ) elseif datecode == 'YMDHM' then timeStamp = string.format('%04i-%02i-%02i %02i:%02i', datenum[1], datenum[2], datenum[3], datenum[4], datenum[5] ) elseif datecode:sub(1,3)=='YMD' then timeStamp = string.format('%04i-%02i-%02i', datenum[1], datenum[2], datenum[3] ) datecode = 'YMD' -- 'YMD', 'YMDHMS' and 'YMDHM' are the only supported format starting with 'YMD'. All others will be converted to 'YMD' elseif datecode == 'YM' then timeStamp = string.format('%04i-%02i', datenum[1], datenum[2] ) elseif datecode:sub(1,1)=='Y' then timeStamp = string.format('%04i', datenum[1] ) datecode = 'Y' elseif datecode == 'M' then timeStamp = string.format('%04i-%02i-%02i', 2000, datenum[2], 1 ) elseif datecode == 'MD' then timeStamp = string.format('%04i-%02i-%02i', 2000, datenum[2], datenum[3] ) else timeStamp = nil -- format not supported end return timeStamp, datecode end --------------------------------------------------------------------------------------- -- trim leading zeros in years prior to year 1000 -- INPUT: -- * datestr - translated date string -- * lang - language of translation -- OUTPUT: -- * datestr - updated date string local function trimYear(datestr, year, lang) local yearStr0, yearStr1, yearStr2, zeroStr yearStr0 = string.format('%04i', year ) -- 4 digit year in standard form "0123" yearStr1 = mw.language.new(lang):formatDate( 'Y', yearStr0) -- same as calling {{#time}} parser function --yearStr1 = mw.getCurrentFrame():callParserFunction( "#time", { 'Y', yearStr0, lang } ) -- translate to a language if yearStr0==yearStr1 then -- most of languages use standard form of year yearStr2 = tostring(year) else -- some languages use different characters for numbers yearStr2 = yearStr1 zeroStr = mw.ustring.sub(yearStr1,1,1) -- get "0" in whatever language for i=1,3 do -- trim leading zeros if mw.ustring.sub(yearStr2,1,1)==zeroStr then yearStr2 = mw.ustring.sub(yearStr2, 2, 5-i) else break end end end return string.gsub(datestr, yearStr1, yearStr2 ) -- in datestr replace long year with trimmed one end --------------------------------------------------------------------------------------- -- Look up proper format string to be passed to {{#time}} parser function -- INPUTS: -- * datecode: YMDHMS, YMDHM, YMD, YM, MD, Y, or M -- * day : Number between 1 and 31 (not needed for most languages) -- * lang : language -- OUTPUT: -- * dFormat : input to {{#time}} function local function getDateFormat(datecode, day, lang) local function parseFormat(dFormat, day) if dFormat:find('default') and #dFormat>10 then -- special (and messy) case of dFormat code depending on a day number -- then json contains a string with more json containing "default" field and 2 digit day keys -- if desired day is not in that json than use "default" case dFormat = dFormat:gsub('”','"') -- change fancy double quote to a straight one, used for json marking local D = mw.text.jsonDecode( dFormat ) --com = mw.dumpObject(D) day = string.format('d%02i',day) -- create day key dFormat = D[day] or D.default dFormat = dFormat:gsub("'", '"') -- change single quote to a double quote, used for {{#time}} marking end return dFormat end local T = {} local tab = mw.ext.data.get('DateI18n.tab', lang) for _, row in pairs(tab.data) do -- convert the output into a dictionary table local id, _, msg = unpack(row) T[id] = msg end local dFormat = T[datecode] if dFormat=='default' and (datecode=='YMDHMS' or datecode=='YMDHM') then -- for most languages adding hour:minute:second is done by adding ", HH:MM:SS to the -- day precission date, those languages are skipped in DateI18n.tab and default to -- English which stores word "default" dFormat = parseFormat(T['YMD'], day).. ', H:i' if datecode=='YMDHMS' then dFormat = dFormat .. ':s' end else dFormat = parseFormat(dFormat, day) end return dFormat end --------------------------------------------------------------------------------------- -- Look up proper format string to be passed to {{#time}} parser function -- INPUTS: -- * month : month number -- * case : gramatic case abbriviation, like "ins", "loc" -- * lang : language -- OUTPUT: -- * dFormat : input to {{#time}} function local function MonthCase(month, case, lang) local T = {{},{},{},{},{},{},{},{},{},{},{},{}} local tab = mw.ext.data.get('I18n/MonthCases.tab', lang) for _, row in pairs(tab.data) do local mth, cs, msg = unpack(row) T[mth][cs] = msg end return T[month][case] end -- ================================================== -- === External functions =========================== -- ================================================== local p = {} --[[ ======================================================================================== Date This function is the core part of the ISOdate template. Usage: local Date = require('Module:DateI18n')._Date local dateStr = Date({2020, 12, 30, 12, 20, 11}, lang) Parameters: * {year,month,day,hour,minute,second, tzhour, tzmin}: broken down date-time component strings or numbers tzhour, tzmin are timezone offsets from UTC, hours and minutes * lang: The language to display it in * case: Language format (genitive, etc.) for some languages * class: CSS class for the <time> node, use "" for no metadata at all ]] function p._Date(datevec, lang, case, class, trim_year) -- make sure inputs are in the right format if not lang or not mw.language.isValidCode( lang ) then lang = mw.getCurrentFrame():callParserFunction( "int", "lang" ) -- get user's chosen language end if lang == 'be-tarsk' then lang = 'be-x-old' end -- process datevec and extract timeStamp and datecode strings as well as numeric datenum array local datecode, datenum = parserDatevec(datevec) local year, month, day = datenum[1], datenum[2], datenum[3] local timeStamp, datecode = getTimestamp(datecode, datenum) if not timeStamp then -- something went wrong in parserDatevec return '' end -- Commons [[Data:DateI18n.tab]] page stores prefered formats for diferent -- languages and datecodes (specifying year-month-day or just year of month-day, etc) -- Look up country specific format input to {{#time}} function local dFormat = getDateFormat(datecode, day, lang) -- By default the gramatical case is not specified (case=='') allowing the format to be specified -- in [[Data:DateI18n.tab]]. You can overwrite the default grammatical case of the month by -- specifying "case" variable. This is needed mostly by Slavic languages to create more complex -- phrases as it is done in [[c:Module:Complex date]] case = case or '' if (lang=='qu' or lang=='qug') and (case=='nom') then -- Special case related to Quechua and Kichwa languages. The form in the I18n is -- Genitive case with suffix "pi" added to month names provided by {#time}} -- in Nominative case that "pi" should be removed -- see https://commons.wikimedia.org/wiki/Template_talk:Date#Quechua from 2014 dFormat = dFormat:gsub('F"pi"', 'F') elseif (case=='gen') then dFormat = strReplace(dFormat, "F", "xg") elseif (case=='nom') then dFormat = strReplace(dFormat, "xg", "F") elseif (case ~= '') then -- see is page [[Data:I18n/MonthCases.tab]] on Commons have name of the month -- in specific gramatic case in desired language. If we have it than replace -- "F" and xg" in dFormat local monthMsg = MonthCase(month, case, lang) if monthMsg and monthMsg ~= '' then -- make sure it exists dFormat = strReplace(dFormat, 'F', '"'..monthMsg..'"') -- replace default month with month name we already looked up dFormat = strReplace(dFormat, 'xg', '"'..monthMsg..'"') end end -- Translate the date using specified format -- See https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#mw.language:formatDate and -- https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions##time for explanation of the format local datestr = mw.language.new(lang):formatDate( dFormat, timeStamp) -- same as using {{#time}} parser function -- Special case related to Thai solar calendar: prior to 1940 new-year was at different -- time of year, so just year (datecode=='Y') is ambiguous and is replaced by "YYYY or YYYY" phrase if lang=='th' and datecode=='Y' and year<=1940 then datestr = string.format('%04i หรือ %04i', year+542, year+543 ) end -- If year<1000 than either keep the date padded to the length of 4 digits or trim it -- decide if the year will stay padded with zeros (for years in 0-999 range) if year and year<1000 then if type(trim_year)=='nil' then trim_year = '100-999' end local trim = yesno(trim_year,nil) -- convert to boolean if trim==nil and type(trim_year)=='string' then -- if "trim_year" not a simple True/False than it is range of dates -- for example '100-999' means to pad one and 2 digit years to be 4 digit long, while keeping 3 digit years as is local YMin, YMax = trim_year:match( '(%d+)-(%d+)' ) trim = (YMin~=nil and year>=tonumber(YMin) and year<=tonumber(YMax)) end if trim==true then datestr = trimYear(datestr, year, lang) -- in datestr replace long year with trimmed one end end -- append timezone if present if datenum[7] and (datecode == 'YMDHMS' or datecode == 'YMDHM') then -- use {{#time}} parser function to create timezone string, so that we use correct character set local sign = (datenum[7]<0) and '−' or '+' timeStamp = string.format("2000-01-01 %02i:%02i:00", math.abs(datenum[7]), datenum[8] or 0) local timezone = mw.language.new(lang):formatDate( 'H:i', timeStamp) -- same as using {{#time}} parser function datestr = string.format("%s %s%s", datestr, sign, timezone ) end -- html formating and tagging of date string if class and class ~= '' and datecode~='M' and datecode~='MD'then local DateHtmlTags = '<span style="white-space:nowrap"><time class="%s" datetime="%s">%s</time></span>' datestr = DateHtmlTags:format(class, timeStamp, datestr) end return datestr end --[[ ======================================================================================== Date This function is the core part of the ISOdate template. Usage: {{#invoke:DateI18n|Date|year=|month=|day=|hour=|minute=|second=|tzhour=|tzmin=|lang=en}} Parameters: * year, month, day, hour, minute, second: broken down date-time component strings * tzhour, tzmin: timezone offset from UTC, hours and minutes * lang: The language to display it in * case: Language format (genitive, etc.) for some languages * class: CSS class for the <time> node, use "" for no metadata at all ]] function p.Date(frame) local args = {} for name, value in pairs( frame.args ) do name = string.gsub( string.lower(name), ' ', '_') args[name] = value end return p._Date( { args.year, args.month, args.day, args.hour, args.minute, args.second, args.tzhour, args.tzmin }, args.lang, -- language args.case, -- allows to specify grammatical case for the month for languages that use them args.class or 'dtstart', -- allows to set the html class of the time node where the date is included. This is useful for microformats. args.trim_year or '100-999' -- by default pad one and 2 digit years to be 4 digit long, while keeping 3 digit years as is ) end return p ber4mpu06s9vonpspt2y1ttwq6gczp6 803620 803619 2020-04-30T20:54:26Z en>RexxS 0 Reverted to revision 879901483 by [[Special:Contributions/Ferret|Ferret]] ([[User talk:Ferret|talk]]): Restore last working version ([[WP:TW|TW]]) 803620 Scribunto text/plain --[[ This module is intended for processing of date strings. Copied from https://commons.wikimedia.org/w/index.php?title=Module:Date&oldid=224728211 Used by Module:ISOdate, Module:Complex date, Module:WikidataIB Authors and maintainers: * User:Parent5446 - original version of the function mimicking template:ISOdate * User:Jarekt - original version of the functions mimicking template:Date and template:ISOyear ]] local p = {} -- ======================================= -- === Dependencies ====================== -- ======================================= local i18n = require('Module:I18n/date') -- get localized translations of date formats local yesno = require('Module:Yesno') local function langSwitch(list,lang) local langList = mw.language.getFallbacksFor(lang) table.insert(langList,1,lang) for i,language in ipairs(langList) do if list[language] then return list[language] end end end --[[ Date This function can be used to provide an ISOdate template. Usage: {{#invoke:Date|Date|year=|month=|day=|hour=|minute=|second=|tzhour=|tzmin=|lang=en}} Parameters: year,month,day,hour,minute,second: broken down date-time component strings tzhour, tzmin: timezone offset from UTC, hours and minutes lang: The language to display it in case: Language format (genitive, etc.) for some languages class: CSS class for the <time> node, use "" for no metadata at all Error Handling: ]] function p.Date(frame) local args = frame.args if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then args.lang = frame:callParserFunction( "int", "lang" ) -- get user's chosen language end return p._Date( { args.year or '', args.month or '', args.day or '', args.hour or '', args.minute or '', args.second or '', args.tzhour or '', args.tzmin or '' }, args.lang, -- language args.case or '', -- allows to specify grammatical case for the month for languages that use them args.class or 'dtstart', -- allows to set the html class of the time node where the date is included. This is useful for microformats. args.trim_year or '100-999' -- by default pad one and 2 digit years to be 4 digit long, while keeping 3 digit years as is ) end function p._Date(datevec, lang, case, class, trim_year) -- make sure inputs are in the right format for i = #datevec + 1, 8 do datevec[i] = '' end if not case then case = '' end if not class then class = '' end if not trim_year then trim_year = '100-999' end -- if language is not provided than look up users language -- WARNING: This step should be done by the template as it does not seem to work as well here (cache issues?) if not lang or not mw.language.isValidCode( lang ) then lang = 'en' end -- Just in case someone broke the internationalization code than fix the english defaults if i18n.DateLang['en'] == nil then i18n.DateLang['en'] = 'en-form' end if i18n.DateFormat['en-form'] == nil then i18n.DateFormat['en-form'] = {YMDHMS='j F Y, H:i:s', YMDHM='j F Y, H:i', YMD='j F Y', YM='F Y', MD='j F', Y='Y'} end -- create datecode based on which variables are provided and check for out of bound values local maxval = {9999, 12, 31, 23, 59, 60, 23, 59} -- max values for year, month, ... local c = {'Y', 'M', 'D', 'H', 'M', 'S', '', ''} local datecode = '' -- a string signifying which combination of variables was provided local datenum = {} -- date-time encoded as a vector = [year, month, ... , second] for i, v in ipairs( datevec ) do if v~=nil and v~='' then datecode = datecode .. c[i] datenum[i] = tonumber(v) if datenum[i]==nil and i==2 then -- month is not a number -> check if it is a month name in English v = mw.language.new('en'):formatDate( "n", v) datenum[i] = tonumber(v) end if datenum[i]==nil or datenum[i]>maxval[i] then -- Some numbers are out of range -> abort and return the empty string return '' end end end -- create time stamp string (for example 2000-02-20 02:20:20) based on which variables were provided local timeStamp if datecode == 'YMDHMS' then timeStamp = string.format('%04i-%02i-%02i %02i:%02i:%02i', datenum[1], datenum[2], datenum[3], datenum[4], datenum[5], datenum[6] ) elseif datecode == 'YMDHM' then timeStamp = string.format('%04i-%02i-%02i %02i:%02i', datenum[1], datenum[2], datenum[3], datenum[4], datenum[5] ) elseif datecode:sub(1,3)=='YMD' then timeStamp = string.format('%04i-%02i-%02i', datenum[1], datenum[2], datenum[3] ) datecode = 'YMD' -- 'YMD', 'YMDHMS' and 'YMDHM' are the only supported format starting with 'YMD'. All others will be converted to 'YMD' elseif datecode == 'YM' then timeStamp = string.format('%04i-%02i', datenum[1], datenum[2] ) elseif datecode:sub(1,1)=='Y' then timeStamp = string.format('%04i', datenum[1] ) datecode = 'Y' elseif datecode == 'M' then timeStamp = string.format('%04i-%02i-%02i', 2000, datenum[2], 1 ) class = '' -- date not complete -> no html formating or micro-tagging of date string elseif datecode == 'MD' then timeStamp = string.format('%04i-%02i-%02i', 2000, datenum[2], datenum[3] ) class = '' -- date not complete -> no html formating or micro-tagging of date string else return '' -- format not supported end -- ========================================================== -- === Create Date String using in chosen language -- ========================================================== -- which form should the date take? -- Use langSwitch to pick formating for each language local langDateForm = langSwitch(i18n.DateLang, lang) -- special case of French and Gallic dates, which require different date format for the 1st day of the month if datenum[3]==1 and (langDateForm=='fr-form' or langDateForm=='ga-form') then langDateForm = langDateForm .. '1' -- ordinal form for the first day of the month end -- special case of Basque dates, which require different date format for the 1st, 11th, 21st and 31st day of the month if langDateForm=='eu-form' then if (datenum[3]==1 or datenum[3]==21) then langDateForm = 'eu-form01' elseif (datenum[3]==11 or datenum[3]==31) then langDateForm = 'eu-form11' end end -- Look up country specific format input to {{#time}} function local dFormat = i18n.DateFormat[langDateForm][datecode] -- overwrite default grammatical case of the month (applies mostly to Slavic languages) if (case=='gen') then -- CAUTION: at the moment i18n.DateFormat uses "F" only as month name, but this might change and this operation does not check if 'F' is in "" brackets or not, so if some language starts using 'F' in "" than this will not work for that language dFormat = dFormat:gsub("F", "xg"); end if (case=='nom') then -- CAUTION: at the moment i18n.DateFormat uses "xg" only as month name, but this might change and this operation does not check if 'xg' is in "" brackets or not, so if some language starts using 'xg' in "" than this will not work for that language dFormat = dFormat:gsub("xg", "F"); end if ((lang=='ru' or lang=='pl' or lang=='cs' or lang=='sl' or lang=='sk') and (case=='loc' or case=='ins')) or (lang=='fi' and (case=='ptv' or case=='ine'or case=='ela'or case=='ill') ) then local monthEn = mw.language.new('en'):formatDate( "F", timeStamp) -- month name in English -- month name using proper case and language. It relies on messages stored in MediaWiki namespace for some cases and languages -- That is why this IF statement uses "lang" not "langDateForm" variable to decide local monthMsg = mw.message.new( string.format('%s-%s', monthEn, case ) ):inLanguage( lang ) if not monthMsg:isDisabled() then -- make sure it exists local month=monthMsg:plain() dFormat = dFormat:gsub('F', '"'..month..'"'); -- replace default month with month name we already looked up dFormat = dFormat:gsub('xg', '"'..month..'"'); end end -- Special case related to Quechua and Kichwa languages -- see https://commons.wikimedia.org/wiki/Template_talk:Date#Quechua from 2014 if (lang=='qu' or lang=='qug') and case=='nom' then dFormat = dFormat:gsub('F"pi"', 'F'); end -- Lua only date formating using {{#time}} parser function (new) -- prefered call which gives "Lua error: too many language codes requested." on the [[Module talk:Date/sandbox/testcases]] page --local datestr = mw.language.new(lang):formatDate( dFormat, timeStamp) local datestr = mw.getCurrentFrame():callParserFunction( "#time", { dFormat, timeStamp, lang } ) -- Another special case related to Thai solar calendar if lang=='th' and datenum[1]~= nil and datenum[1]<=1940 then -- As of 2014 {{#time}} parser function did not resolve those cases properly -- See https://en.wikipedia.org/wiki/Thai_solar_calendar#New_year for reference -- Disable once https://bugzilla.wikimedia.org/show_bug.cgi?id=66648 is fixed if datecode=='Y' then -- date is ambiguous datestr = string.format('%04i หรือ %04i', datenum[1]+542, datenum[1]+543 ) elseif datenum[2]<=3 then -- year is wrong (one too many) datestr = datestr:gsub( string.format('%04i', datenum[1]+543), string.format('%04i', datenum[1]+542 ) ) end end -- If year<1000 than either keep it padded to the length of 4 digits or trim it -- decide if the year will stay padded with zeros (for years in 0-999 range) if datenum[1]~= nil and datenum[1]<1000 then local trim = yesno(trim_year,nil) if trim == nil then local YMin, YMax = trim_year:match( '(%d+)-(%d+)' ) trim = (YMin~=nil and datenum[1]>=tonumber(YMin) and datenum[1]<=tonumber(YMax)) end -- If the date form isn't the Thai solar calendar, don't zero pad years in the range of 100-999. -- If at some point support for Islamic/Hebrew/Japanese years is added, they may need to be skipped as well. if trim then --local yearStr1 = mw.language.new(lang):formatDate( 'Y', timeStamp) local yearStr1 = mw.getCurrentFrame():callParserFunction( "#time", { 'Y', timeStamp, lang } ) --local yearStr1 = datestr:match( '%d%d%d%d' ) -- 4 digits in a row (in any language) - that must be a year local yearStr2 = yearStr1 local zeroStr = mw.ustring.sub(yearStr1,1,1) for i=1,3 do -- trim leading zeros if mw.ustring.sub(yearStr2,1,1)==zeroStr then yearStr2 = mw.ustring.sub(yearStr2, 2, 5-i) else break end end datestr = datestr:gsub( yearStr1, yearStr2 ) --datestr = string.format('%s (%s, %s)', datestr, yearStr1, yearStr2 ) end end -- append timezone if present if datevec[7] ~= '' and (datecode == 'YMDHMS' or datecode == 'YMDHM') then local tzstr, tzhournum = '', tonumber(datevec[7]) if tzhournum < 0 then tzstr = '−' else tzstr = '+' end tzstr = tzstr..string.format("%02d", math.abs(tzhournum))..':' if datevec[8] ~= '' then tzstr = tzstr..datevec[8] else tzstr = tzstr..'00' end datestr = datestr..' '..tzstr end -- html formating and tagging of date string if class ~= '' then local DateHtmlTags = '<span style="white-space:nowrap"><time class="%s" datetime="%s">%s</time></span>' datestr = DateHtmlTags:format(class, timeStamp, datestr) end return datestr end return p 2qhdyl056nbwaexn38ogtoc8cvnzot8 803621 803620 2020-05-14T21:05:37Z en>RexxS 0 Undid revision 954131094 by [[Special:Contributions/RexxS|RexxS]] ([[User talk:RexxS|talk]]) errors should be fixed (sandboxes working) - try new version again 803621 Scribunto text/plain --[[ __ __ _ _ ____ _ ___ _ ___ | \/ | ___ __| |_ _| | ___ _| _ \ __ _| |_ ___|_ _/ |( _ ) _ __ | |\/| |/ _ \ / _` | | | | |/ _ (_) | | |/ _` | __/ _ \| || |/ _ \| '_ \ | | | | (_) | (_| | |_| | | __/_| |_| | (_| | || __/| || | (_) | | | | |_| |_|\___/ \__,_|\__,_|_|\___(_)____/ \__,_|\__\___|___|_|\___/|_| |_| This module is intended for processing of date strings. Please do not modify this code without applying the changes first at Module:Date/sandbox and testing at Module:Date/sandbox/testcases and Module talk:Date/sandbox/testcases. Authors and maintainers: * User:Parent5446 - original version of the function mimicking template:ISOdate * User:Jarekt - original version of the functions mimicking template:Date ]] require('Module:No globals') -- ================================================== -- === Internal functions =========================== -- ================================================== -- Function allowing for consistent treatment of boolean-like wikitext input. -- It works similarly to Module:Yesno local function yesno(val, default) if type(val) == 'boolean' then return val elseif type(val) == 'number' then if val==1 then return true elseif val==0 then return false end elseif type(val) == 'string' then val = mw.ustring.lower(val) -- put in lower case if val == 'no' or val == 'n' or val == 'false' or tonumber(val) == 0 then return false elseif val == 'yes' or val == 'y' or val == 'true' or tonumber(val) == 1 then return true end end return default end --------------------------------------------------------------------------------------- -- String replacement that ignores part of the string in "..." local function strReplace(String, old, new) if String:find('"') then local T={} for i, str in ipairs(mw.text.split( String, '"', true )) do if i%2==1 then str = str:gsub(old, new) end table.insert(T, str) end return table.concat(T,'"') else return String:gsub(old, new) end end --------------------------------------------------------------------------------------- -- process datevec -- INPUT: -- * datevec - Array of {year,month,day,hour,minute,second, tzhour, tzmin} containing broken -- down date-time component strings or numbers -- OUTPUT: -- * datecode - a code specifying content of the array where Y' is year, 'M' is month, -- 'D' is day, 'H' is hour, 'M' minute, 'S' is second. output has to be one of YMDHMS, YMDHM, YMD, YM, MD, Y -- * datenum - same array but holding only numbers or nuls local function parserDatevec(datevec) -- if month is not a number than check if it is a month name in project's language local month = datevec[2] if month and month~='' and not tonumber(month) then datevec[2] = mw.getContentLanguage():formatDate( "n", month) end -- create datecode based on which variables are provided and check for out-of-bound values local maxval = {nil, 12, 31, 23, 59, 59, 23, 59} -- max values for year, month, ... local minval = {nil, 1, 1, 0, 0, 0, -23, 0} -- min values for year, month, ... local c = {'Y', 'M', 'D', 'H', 'M', 'S', '', ''} local datecode = '' -- a string signifying which combination of variables was provided local datenum = {} -- date-time encoded as a vector = [year, month, ... , second] for i = 1,8 do datenum[i] = tonumber(datevec[i]) if datenum[i] and (i==1 or (datenum[i]>=minval[i] and datenum[i]<=maxval[i])) then datecode = datecode .. c[i] end end return datecode, datenum end --------------------------------------------------------------------------------------- -- process datevec -- INPUT: -- * datecode - a code specifying content of the array where Y' is year, 'M' is month, -- 'D' is day, 'H' is hour, 'M' minute, 'S' is second. output has to be one of YMDHMS, YMDHM, YMD, YM, MD, Y -- * datenum - Array of {year,month,day,hour,minute,second, tzhour, tzmin} as numbers or nuls -- OUTPUT: -- * timeStamp - date string in the format taken by mw.language:formatDate lua function and {{#time}} perser function -- https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#mw.language:formatDate -- https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions#.23time -- * datecode - with possible corrections local function getTimestamp(datecode, datenum) -- create time stamp string (for example 2000-02-20 02:20:20) based on which variables were provided local timeStamp if datecode == 'YMDHMS' then timeStamp = string.format('%04i-%02i-%02i %02i:%02i:%02i', datenum[1], datenum[2], datenum[3], datenum[4], datenum[5], datenum[6] ) elseif datecode == 'YMDHM' then timeStamp = string.format('%04i-%02i-%02i %02i:%02i', datenum[1], datenum[2], datenum[3], datenum[4], datenum[5] ) elseif datecode:sub(1,3)=='YMD' then timeStamp = string.format('%04i-%02i-%02i', datenum[1], datenum[2], datenum[3] ) datecode = 'YMD' -- 'YMD', 'YMDHMS' and 'YMDHM' are the only supported format starting with 'YMD'. All others will be converted to 'YMD' elseif datecode == 'YM' then timeStamp = string.format('%04i-%02i', datenum[1], datenum[2] ) elseif datecode:sub(1,1)=='Y' then timeStamp = string.format('%04i', datenum[1] ) datecode = 'Y' elseif datecode == 'M' then timeStamp = string.format('%04i-%02i-%02i', 2000, datenum[2], 1 ) elseif datecode == 'MD' then timeStamp = string.format('%04i-%02i-%02i', 2000, datenum[2], datenum[3] ) else timeStamp = nil -- format not supported end return timeStamp, datecode end --------------------------------------------------------------------------------------- -- trim leading zeros in years prior to year 1000 -- INPUT: -- * datestr - translated date string -- * lang - language of translation -- OUTPUT: -- * datestr - updated date string local function trimYear(datestr, year, lang) local yearStr0, yearStr1, yearStr2, zeroStr yearStr0 = string.format('%04i', year ) -- 4 digit year in standard form "0123" yearStr1 = mw.language.new(lang):formatDate( 'Y', yearStr0) -- same as calling {{#time}} parser function --yearStr1 = mw.getCurrentFrame():callParserFunction( "#time", { 'Y', yearStr0, lang } ) -- translate to a language if yearStr0==yearStr1 then -- most of languages use standard form of year yearStr2 = tostring(year) else -- some languages use different characters for numbers yearStr2 = yearStr1 zeroStr = mw.ustring.sub(yearStr1,1,1) -- get "0" in whatever language for i=1,3 do -- trim leading zeros if mw.ustring.sub(yearStr2,1,1)==zeroStr then yearStr2 = mw.ustring.sub(yearStr2, 2, 5-i) else break end end end return string.gsub(datestr, yearStr1, yearStr2 ) -- in datestr replace long year with trimmed one end --------------------------------------------------------------------------------------- -- Look up proper format string to be passed to {{#time}} parser function -- INPUTS: -- * datecode: YMDHMS, YMDHM, YMD, YM, MD, Y, or M -- * day : Number between 1 and 31 (not needed for most languages) -- * lang : language -- OUTPUT: -- * dFormat : input to {{#time}} function local function getDateFormat(datecode, day, lang) local function parseFormat(dFormat, day) if dFormat:find('default') and #dFormat>10 then -- special (and messy) case of dFormat code depending on a day number -- then json contains a string with more json containing "default" field and 2 digit day keys -- if desired day is not in that json than use "default" case dFormat = dFormat:gsub('”','"') -- change fancy double quote to a straight one, used for json marking local D = mw.text.jsonDecode( dFormat ) --com = mw.dumpObject(D) day = string.format('d%02i',day) -- create day key dFormat = D[day] or D.default dFormat = dFormat:gsub("'", '"') -- change single quote to a double quote, used for {{#time}} marking end return dFormat end local T = {} local tab = mw.ext.data.get('DateI18n.tab', lang) for _, row in pairs(tab.data) do -- convert the output into a dictionary table local id, _, msg = unpack(row) T[id] = msg end local dFormat = T[datecode] if dFormat=='default' and (datecode=='YMDHMS' or datecode=='YMDHM') then -- for most languages adding hour:minute:second is done by adding ", HH:MM:SS to the -- day precission date, those languages are skipped in DateI18n.tab and default to -- English which stores word "default" dFormat = parseFormat(T['YMD'], day).. ', H:i' if datecode=='YMDHMS' then dFormat = dFormat .. ':s' end else dFormat = parseFormat(dFormat, day) end return dFormat end --------------------------------------------------------------------------------------- -- Look up proper format string to be passed to {{#time}} parser function -- INPUTS: -- * month : month number -- * case : gramatic case abbriviation, like "ins", "loc" -- * lang : language -- OUTPUT: -- * dFormat : input to {{#time}} function local function MonthCase(month, case, lang) local T = {{},{},{},{},{},{},{},{},{},{},{},{}} local tab = mw.ext.data.get('I18n/MonthCases.tab', lang) for _, row in pairs(tab.data) do local mth, cs, msg = unpack(row) T[mth][cs] = msg end return T[month][case] end -- ================================================== -- === External functions =========================== -- ================================================== local p = {} --[[ ======================================================================================== Date This function is the core part of the ISOdate template. Usage: local Date = require('Module:DateI18n')._Date local dateStr = Date({2020, 12, 30, 12, 20, 11}, lang) Parameters: * {year,month,day,hour,minute,second, tzhour, tzmin}: broken down date-time component strings or numbers tzhour, tzmin are timezone offsets from UTC, hours and minutes * lang: The language to display it in * case: Language format (genitive, etc.) for some languages * class: CSS class for the <time> node, use "" for no metadata at all ]] function p._Date(datevec, lang, case, class, trim_year) -- make sure inputs are in the right format if not lang or not mw.language.isValidCode( lang ) then lang = mw.getCurrentFrame():callParserFunction( "int", "lang" ) -- get user's chosen language end if lang == 'be-tarsk' then lang = 'be-x-old' end -- process datevec and extract timeStamp and datecode strings as well as numeric datenum array local datecode, datenum = parserDatevec(datevec) local year, month, day = datenum[1], datenum[2], datenum[3] local timeStamp, datecode = getTimestamp(datecode, datenum) if not timeStamp then -- something went wrong in parserDatevec return '' end -- Commons [[Data:DateI18n.tab]] page stores prefered formats for diferent -- languages and datecodes (specifying year-month-day or just year of month-day, etc) -- Look up country specific format input to {{#time}} function local dFormat = getDateFormat(datecode, day, lang) -- By default the gramatical case is not specified (case=='') allowing the format to be specified -- in [[Data:DateI18n.tab]]. You can overwrite the default grammatical case of the month by -- specifying "case" variable. This is needed mostly by Slavic languages to create more complex -- phrases as it is done in [[c:Module:Complex date]] case = case or '' if (lang=='qu' or lang=='qug') and (case=='nom') then -- Special case related to Quechua and Kichwa languages. The form in the I18n is -- Genitive case with suffix "pi" added to month names provided by {#time}} -- in Nominative case that "pi" should be removed -- see https://commons.wikimedia.org/wiki/Template_talk:Date#Quechua from 2014 dFormat = dFormat:gsub('F"pi"', 'F') elseif (case=='gen') then dFormat = strReplace(dFormat, "F", "xg") elseif (case=='nom') then dFormat = strReplace(dFormat, "xg", "F") elseif (case ~= '') then -- see is page [[Data:I18n/MonthCases.tab]] on Commons have name of the month -- in specific gramatic case in desired language. If we have it than replace -- "F" and xg" in dFormat local monthMsg = MonthCase(month, case, lang) if monthMsg and monthMsg ~= '' then -- make sure it exists dFormat = strReplace(dFormat, 'F', '"'..monthMsg..'"') -- replace default month with month name we already looked up dFormat = strReplace(dFormat, 'xg', '"'..monthMsg..'"') end end -- Translate the date using specified format -- See https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#mw.language:formatDate and -- https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions##time for explanation of the format local datestr = mw.language.new(lang):formatDate( dFormat, timeStamp) -- same as using {{#time}} parser function -- Special case related to Thai solar calendar: prior to 1940 new-year was at different -- time of year, so just year (datecode=='Y') is ambiguous and is replaced by "YYYY or YYYY" phrase if lang=='th' and datecode=='Y' and year<=1940 then datestr = string.format('%04i หรือ %04i', year+542, year+543 ) end -- If year<1000 than either keep the date padded to the length of 4 digits or trim it -- decide if the year will stay padded with zeros (for years in 0-999 range) if year and year<1000 then if type(trim_year)=='nil' then trim_year = '100-999' end local trim = yesno(trim_year,nil) -- convert to boolean if trim==nil and type(trim_year)=='string' then -- if "trim_year" not a simple True/False than it is range of dates -- for example '100-999' means to pad one and 2 digit years to be 4 digit long, while keeping 3 digit years as is local YMin, YMax = trim_year:match( '(%d+)-(%d+)' ) trim = (YMin~=nil and year>=tonumber(YMin) and year<=tonumber(YMax)) end if trim==true then datestr = trimYear(datestr, year, lang) -- in datestr replace long year with trimmed one end end -- append timezone if present if datenum[7] and (datecode == 'YMDHMS' or datecode == 'YMDHM') then -- use {{#time}} parser function to create timezone string, so that we use correct character set local sign = (datenum[7]<0) and '−' or '+' timeStamp = string.format("2000-01-01 %02i:%02i:00", math.abs(datenum[7]), datenum[8] or 0) local timezone = mw.language.new(lang):formatDate( 'H:i', timeStamp) -- same as using {{#time}} parser function datestr = string.format("%s %s%s", datestr, sign, timezone ) end -- html formating and tagging of date string if class and class ~= '' and datecode~='M' and datecode~='MD'then local DateHtmlTags = '<span style="white-space:nowrap"><time class="%s" datetime="%s">%s</time></span>' datestr = DateHtmlTags:format(class, timeStamp, datestr) end return datestr end --[[ ======================================================================================== Date This function is the core part of the ISOdate template. Usage: {{#invoke:DateI18n|Date|year=|month=|day=|hour=|minute=|second=|tzhour=|tzmin=|lang=en}} Parameters: * year, month, day, hour, minute, second: broken down date-time component strings * tzhour, tzmin: timezone offset from UTC, hours and minutes * lang: The language to display it in * case: Language format (genitive, etc.) for some languages * class: CSS class for the <time> node, use "" for no metadata at all ]] function p.Date(frame) local args = {} for name, value in pairs( frame.args ) do name = string.gsub( string.lower(name), ' ', '_') args[name] = value end return p._Date( { args.year, args.month, args.day, args.hour, args.minute, args.second, args.tzhour, args.tzmin }, args.lang, -- language args.case, -- allows to specify grammatical case for the month for languages that use them args.class or 'dtstart', -- allows to set the html class of the time node where the date is included. This is useful for microformats. args.trim_year or '100-999' -- by default pad one and 2 digit years to be 4 digit long, while keeping 3 digit years as is ) end return p ber4mpu06s9vonpspt2y1ttwq6gczp6 803622 803621 2022-10-21T06:45:00Z en>Legoktm 0 Replace [[Module:No globals]] with require( "strict" ) 803622 Scribunto text/plain --[[ __ __ _ _ ____ _ ___ _ ___ | \/ | ___ __| |_ _| | ___ _| _ \ __ _| |_ ___|_ _/ |( _ ) _ __ | |\/| |/ _ \ / _` | | | | |/ _ (_) | | |/ _` | __/ _ \| || |/ _ \| '_ \ | | | | (_) | (_| | |_| | | __/_| |_| | (_| | || __/| || | (_) | | | | |_| |_|\___/ \__,_|\__,_|_|\___(_)____/ \__,_|\__\___|___|_|\___/|_| |_| This module is intended for processing of date strings. Please do not modify this code without applying the changes first at Module:Date/sandbox and testing at Module:Date/sandbox/testcases and Module talk:Date/sandbox/testcases. Authors and maintainers: * User:Parent5446 - original version of the function mimicking template:ISOdate * User:Jarekt - original version of the functions mimicking template:Date ]] require('strict') -- ================================================== -- === Internal functions =========================== -- ================================================== -- Function allowing for consistent treatment of boolean-like wikitext input. -- It works similarly to Module:Yesno local function yesno(val, default) if type(val) == 'boolean' then return val elseif type(val) == 'number' then if val==1 then return true elseif val==0 then return false end elseif type(val) == 'string' then val = mw.ustring.lower(val) -- put in lower case if val == 'no' or val == 'n' or val == 'false' or tonumber(val) == 0 then return false elseif val == 'yes' or val == 'y' or val == 'true' or tonumber(val) == 1 then return true end end return default end --------------------------------------------------------------------------------------- -- String replacement that ignores part of the string in "..." local function strReplace(String, old, new) if String:find('"') then local T={} for i, str in ipairs(mw.text.split( String, '"', true )) do if i%2==1 then str = str:gsub(old, new) end table.insert(T, str) end return table.concat(T,'"') else return String:gsub(old, new) end end --------------------------------------------------------------------------------------- -- process datevec -- INPUT: -- * datevec - Array of {year,month,day,hour,minute,second, tzhour, tzmin} containing broken -- down date-time component strings or numbers -- OUTPUT: -- * datecode - a code specifying content of the array where Y' is year, 'M' is month, -- 'D' is day, 'H' is hour, 'M' minute, 'S' is second. output has to be one of YMDHMS, YMDHM, YMD, YM, MD, Y -- * datenum - same array but holding only numbers or nuls local function parserDatevec(datevec) -- if month is not a number than check if it is a month name in project's language local month = datevec[2] if month and month~='' and not tonumber(month) then datevec[2] = mw.getContentLanguage():formatDate( "n", month) end -- create datecode based on which variables are provided and check for out-of-bound values local maxval = {nil, 12, 31, 23, 59, 59, 23, 59} -- max values for year, month, ... local minval = {nil, 1, 1, 0, 0, 0, -23, 0} -- min values for year, month, ... local c = {'Y', 'M', 'D', 'H', 'M', 'S', '', ''} local datecode = '' -- a string signifying which combination of variables was provided local datenum = {} -- date-time encoded as a vector = [year, month, ... , second] for i = 1,8 do datenum[i] = tonumber(datevec[i]) if datenum[i] and (i==1 or (datenum[i]>=minval[i] and datenum[i]<=maxval[i])) then datecode = datecode .. c[i] end end return datecode, datenum end --------------------------------------------------------------------------------------- -- process datevec -- INPUT: -- * datecode - a code specifying content of the array where Y' is year, 'M' is month, -- 'D' is day, 'H' is hour, 'M' minute, 'S' is second. output has to be one of YMDHMS, YMDHM, YMD, YM, MD, Y -- * datenum - Array of {year,month,day,hour,minute,second, tzhour, tzmin} as numbers or nuls -- OUTPUT: -- * timeStamp - date string in the format taken by mw.language:formatDate lua function and {{#time}} perser function -- https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#mw.language:formatDate -- https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions#.23time -- * datecode - with possible corrections local function getTimestamp(datecode, datenum) -- create time stamp string (for example 2000-02-20 02:20:20) based on which variables were provided local timeStamp if datecode == 'YMDHMS' then timeStamp = string.format('%04i-%02i-%02i %02i:%02i:%02i', datenum[1], datenum[2], datenum[3], datenum[4], datenum[5], datenum[6] ) elseif datecode == 'YMDHM' then timeStamp = string.format('%04i-%02i-%02i %02i:%02i', datenum[1], datenum[2], datenum[3], datenum[4], datenum[5] ) elseif datecode:sub(1,3)=='YMD' then timeStamp = string.format('%04i-%02i-%02i', datenum[1], datenum[2], datenum[3] ) datecode = 'YMD' -- 'YMD', 'YMDHMS' and 'YMDHM' are the only supported format starting with 'YMD'. All others will be converted to 'YMD' elseif datecode == 'YM' then timeStamp = string.format('%04i-%02i', datenum[1], datenum[2] ) elseif datecode:sub(1,1)=='Y' then timeStamp = string.format('%04i', datenum[1] ) datecode = 'Y' elseif datecode == 'M' then timeStamp = string.format('%04i-%02i-%02i', 2000, datenum[2], 1 ) elseif datecode == 'MD' then timeStamp = string.format('%04i-%02i-%02i', 2000, datenum[2], datenum[3] ) else timeStamp = nil -- format not supported end return timeStamp, datecode end --------------------------------------------------------------------------------------- -- trim leading zeros in years prior to year 1000 -- INPUT: -- * datestr - translated date string -- * lang - language of translation -- OUTPUT: -- * datestr - updated date string local function trimYear(datestr, year, lang) local yearStr0, yearStr1, yearStr2, zeroStr yearStr0 = string.format('%04i', year ) -- 4 digit year in standard form "0123" yearStr1 = mw.language.new(lang):formatDate( 'Y', yearStr0) -- same as calling {{#time}} parser function --yearStr1 = mw.getCurrentFrame():callParserFunction( "#time", { 'Y', yearStr0, lang } ) -- translate to a language if yearStr0==yearStr1 then -- most of languages use standard form of year yearStr2 = tostring(year) else -- some languages use different characters for numbers yearStr2 = yearStr1 zeroStr = mw.ustring.sub(yearStr1,1,1) -- get "0" in whatever language for i=1,3 do -- trim leading zeros if mw.ustring.sub(yearStr2,1,1)==zeroStr then yearStr2 = mw.ustring.sub(yearStr2, 2, 5-i) else break end end end return string.gsub(datestr, yearStr1, yearStr2 ) -- in datestr replace long year with trimmed one end --------------------------------------------------------------------------------------- -- Look up proper format string to be passed to {{#time}} parser function -- INPUTS: -- * datecode: YMDHMS, YMDHM, YMD, YM, MD, Y, or M -- * day : Number between 1 and 31 (not needed for most languages) -- * lang : language -- OUTPUT: -- * dFormat : input to {{#time}} function local function getDateFormat(datecode, day, lang) local function parseFormat(dFormat, day) if dFormat:find('default') and #dFormat>10 then -- special (and messy) case of dFormat code depending on a day number -- then json contains a string with more json containing "default" field and 2 digit day keys -- if desired day is not in that json than use "default" case dFormat = dFormat:gsub('”','"') -- change fancy double quote to a straight one, used for json marking local D = mw.text.jsonDecode( dFormat ) --com = mw.dumpObject(D) day = string.format('d%02i',day) -- create day key dFormat = D[day] or D.default dFormat = dFormat:gsub("'", '"') -- change single quote to a double quote, used for {{#time}} marking end return dFormat end local T = {} local tab = mw.ext.data.get('DateI18n.tab', lang) for _, row in pairs(tab.data) do -- convert the output into a dictionary table local id, _, msg = unpack(row) T[id] = msg end local dFormat = T[datecode] if dFormat=='default' and (datecode=='YMDHMS' or datecode=='YMDHM') then -- for most languages adding hour:minute:second is done by adding ", HH:MM:SS to the -- day precission date, those languages are skipped in DateI18n.tab and default to -- English which stores word "default" dFormat = parseFormat(T['YMD'], day).. ', H:i' if datecode=='YMDHMS' then dFormat = dFormat .. ':s' end else dFormat = parseFormat(dFormat, day) end return dFormat end --------------------------------------------------------------------------------------- -- Look up proper format string to be passed to {{#time}} parser function -- INPUTS: -- * month : month number -- * case : gramatic case abbriviation, like "ins", "loc" -- * lang : language -- OUTPUT: -- * dFormat : input to {{#time}} function local function MonthCase(month, case, lang) local T = {{},{},{},{},{},{},{},{},{},{},{},{}} local tab = mw.ext.data.get('I18n/MonthCases.tab', lang) for _, row in pairs(tab.data) do local mth, cs, msg = unpack(row) T[mth][cs] = msg end return T[month][case] end -- ================================================== -- === External functions =========================== -- ================================================== local p = {} --[[ ======================================================================================== Date This function is the core part of the ISOdate template. Usage: local Date = require('Module:DateI18n')._Date local dateStr = Date({2020, 12, 30, 12, 20, 11}, lang) Parameters: * {year,month,day,hour,minute,second, tzhour, tzmin}: broken down date-time component strings or numbers tzhour, tzmin are timezone offsets from UTC, hours and minutes * lang: The language to display it in * case: Language format (genitive, etc.) for some languages * class: CSS class for the <time> node, use "" for no metadata at all ]] function p._Date(datevec, lang, case, class, trim_year) -- make sure inputs are in the right format if not lang or not mw.language.isValidCode( lang ) then lang = mw.getCurrentFrame():callParserFunction( "int", "lang" ) -- get user's chosen language end if lang == 'be-tarsk' then lang = 'be-x-old' end -- process datevec and extract timeStamp and datecode strings as well as numeric datenum array local datecode, datenum = parserDatevec(datevec) local year, month, day = datenum[1], datenum[2], datenum[3] local timeStamp, datecode = getTimestamp(datecode, datenum) if not timeStamp then -- something went wrong in parserDatevec return '' end -- Commons [[Data:DateI18n.tab]] page stores prefered formats for diferent -- languages and datecodes (specifying year-month-day or just year of month-day, etc) -- Look up country specific format input to {{#time}} function local dFormat = getDateFormat(datecode, day, lang) -- By default the gramatical case is not specified (case=='') allowing the format to be specified -- in [[Data:DateI18n.tab]]. You can overwrite the default grammatical case of the month by -- specifying "case" variable. This is needed mostly by Slavic languages to create more complex -- phrases as it is done in [[c:Module:Complex date]] case = case or '' if (lang=='qu' or lang=='qug') and (case=='nom') then -- Special case related to Quechua and Kichwa languages. The form in the I18n is -- Genitive case with suffix "pi" added to month names provided by {#time}} -- in Nominative case that "pi" should be removed -- see https://commons.wikimedia.org/wiki/Template_talk:Date#Quechua from 2014 dFormat = dFormat:gsub('F"pi"', 'F') elseif (case=='gen') then dFormat = strReplace(dFormat, "F", "xg") elseif (case=='nom') then dFormat = strReplace(dFormat, "xg", "F") elseif (case ~= '') then -- see is page [[Data:I18n/MonthCases.tab]] on Commons have name of the month -- in specific gramatic case in desired language. If we have it than replace -- "F" and xg" in dFormat local monthMsg = MonthCase(month, case, lang) if monthMsg and monthMsg ~= '' then -- make sure it exists dFormat = strReplace(dFormat, 'F', '"'..monthMsg..'"') -- replace default month with month name we already looked up dFormat = strReplace(dFormat, 'xg', '"'..monthMsg..'"') end end -- Translate the date using specified format -- See https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#mw.language:formatDate and -- https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions##time for explanation of the format local datestr = mw.language.new(lang):formatDate( dFormat, timeStamp) -- same as using {{#time}} parser function -- Special case related to Thai solar calendar: prior to 1940 new-year was at different -- time of year, so just year (datecode=='Y') is ambiguous and is replaced by "YYYY or YYYY" phrase if lang=='th' and datecode=='Y' and year<=1940 then datestr = string.format('%04i หรือ %04i', year+542, year+543 ) end -- If year<1000 than either keep the date padded to the length of 4 digits or trim it -- decide if the year will stay padded with zeros (for years in 0-999 range) if year and year<1000 then if type(trim_year)=='nil' then trim_year = '100-999' end local trim = yesno(trim_year,nil) -- convert to boolean if trim==nil and type(trim_year)=='string' then -- if "trim_year" not a simple True/False than it is range of dates -- for example '100-999' means to pad one and 2 digit years to be 4 digit long, while keeping 3 digit years as is local YMin, YMax = trim_year:match( '(%d+)-(%d+)' ) trim = (YMin~=nil and year>=tonumber(YMin) and year<=tonumber(YMax)) end if trim==true then datestr = trimYear(datestr, year, lang) -- in datestr replace long year with trimmed one end end -- append timezone if present if datenum[7] and (datecode == 'YMDHMS' or datecode == 'YMDHM') then -- use {{#time}} parser function to create timezone string, so that we use correct character set local sign = (datenum[7]<0) and '−' or '+' timeStamp = string.format("2000-01-01 %02i:%02i:00", math.abs(datenum[7]), datenum[8] or 0) local timezone = mw.language.new(lang):formatDate( 'H:i', timeStamp) -- same as using {{#time}} parser function datestr = string.format("%s %s%s", datestr, sign, timezone ) end -- html formating and tagging of date string if class and class ~= '' and datecode~='M' and datecode~='MD'then local DateHtmlTags = '<span style="white-space:nowrap"><time class="%s" datetime="%s">%s</time></span>' datestr = DateHtmlTags:format(class, timeStamp, datestr) end return datestr end --[[ ======================================================================================== Date This function is the core part of the ISOdate template. Usage: {{#invoke:DateI18n|Date|year=|month=|day=|hour=|minute=|second=|tzhour=|tzmin=|lang=en}} Parameters: * year, month, day, hour, minute, second: broken down date-time component strings * tzhour, tzmin: timezone offset from UTC, hours and minutes * lang: The language to display it in * case: Language format (genitive, etc.) for some languages * class: CSS class for the <time> node, use "" for no metadata at all ]] function p.Date(frame) local args = {} for name, value in pairs( frame.args ) do name = string.gsub( string.lower(name), ' ', '_') args[name] = value end return p._Date( { args.year, args.month, args.day, args.hour, args.minute, args.second, args.tzhour, args.tzmin }, args.lang, -- language args.case, -- allows to specify grammatical case for the month for languages that use them args.class or 'dtstart', -- allows to set the html class of the time node where the date is included. This is useful for microformats. args.trim_year or '100-999' -- by default pad one and 2 digit years to be 4 digit long, while keeping 3 digit years as is ) end return p lcwmkh5p6iicle9dw7qzn6d8j6xkduv 803623 803622 2024-04-29T16:20:41Z en>Pppery 0 Protected "[[Module:DateI18n]]" ([Edit=Require template editor access] (indefinite) [Move=Require template editor access] (indefinite)) 803622 Scribunto text/plain --[[ __ __ _ _ ____ _ ___ _ ___ | \/ | ___ __| |_ _| | ___ _| _ \ __ _| |_ ___|_ _/ |( _ ) _ __ | |\/| |/ _ \ / _` | | | | |/ _ (_) | | |/ _` | __/ _ \| || |/ _ \| '_ \ | | | | (_) | (_| | |_| | | __/_| |_| | (_| | || __/| || | (_) | | | | |_| |_|\___/ \__,_|\__,_|_|\___(_)____/ \__,_|\__\___|___|_|\___/|_| |_| This module is intended for processing of date strings. Please do not modify this code without applying the changes first at Module:Date/sandbox and testing at Module:Date/sandbox/testcases and Module talk:Date/sandbox/testcases. Authors and maintainers: * User:Parent5446 - original version of the function mimicking template:ISOdate * User:Jarekt - original version of the functions mimicking template:Date ]] require('strict') -- ================================================== -- === Internal functions =========================== -- ================================================== -- Function allowing for consistent treatment of boolean-like wikitext input. -- It works similarly to Module:Yesno local function yesno(val, default) if type(val) == 'boolean' then return val elseif type(val) == 'number' then if val==1 then return true elseif val==0 then return false end elseif type(val) == 'string' then val = mw.ustring.lower(val) -- put in lower case if val == 'no' or val == 'n' or val == 'false' or tonumber(val) == 0 then return false elseif val == 'yes' or val == 'y' or val == 'true' or tonumber(val) == 1 then return true end end return default end --------------------------------------------------------------------------------------- -- String replacement that ignores part of the string in "..." local function strReplace(String, old, new) if String:find('"') then local T={} for i, str in ipairs(mw.text.split( String, '"', true )) do if i%2==1 then str = str:gsub(old, new) end table.insert(T, str) end return table.concat(T,'"') else return String:gsub(old, new) end end --------------------------------------------------------------------------------------- -- process datevec -- INPUT: -- * datevec - Array of {year,month,day,hour,minute,second, tzhour, tzmin} containing broken -- down date-time component strings or numbers -- OUTPUT: -- * datecode - a code specifying content of the array where Y' is year, 'M' is month, -- 'D' is day, 'H' is hour, 'M' minute, 'S' is second. output has to be one of YMDHMS, YMDHM, YMD, YM, MD, Y -- * datenum - same array but holding only numbers or nuls local function parserDatevec(datevec) -- if month is not a number than check if it is a month name in project's language local month = datevec[2] if month and month~='' and not tonumber(month) then datevec[2] = mw.getContentLanguage():formatDate( "n", month) end -- create datecode based on which variables are provided and check for out-of-bound values local maxval = {nil, 12, 31, 23, 59, 59, 23, 59} -- max values for year, month, ... local minval = {nil, 1, 1, 0, 0, 0, -23, 0} -- min values for year, month, ... local c = {'Y', 'M', 'D', 'H', 'M', 'S', '', ''} local datecode = '' -- a string signifying which combination of variables was provided local datenum = {} -- date-time encoded as a vector = [year, month, ... , second] for i = 1,8 do datenum[i] = tonumber(datevec[i]) if datenum[i] and (i==1 or (datenum[i]>=minval[i] and datenum[i]<=maxval[i])) then datecode = datecode .. c[i] end end return datecode, datenum end --------------------------------------------------------------------------------------- -- process datevec -- INPUT: -- * datecode - a code specifying content of the array where Y' is year, 'M' is month, -- 'D' is day, 'H' is hour, 'M' minute, 'S' is second. output has to be one of YMDHMS, YMDHM, YMD, YM, MD, Y -- * datenum - Array of {year,month,day,hour,minute,second, tzhour, tzmin} as numbers or nuls -- OUTPUT: -- * timeStamp - date string in the format taken by mw.language:formatDate lua function and {{#time}} perser function -- https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#mw.language:formatDate -- https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions#.23time -- * datecode - with possible corrections local function getTimestamp(datecode, datenum) -- create time stamp string (for example 2000-02-20 02:20:20) based on which variables were provided local timeStamp if datecode == 'YMDHMS' then timeStamp = string.format('%04i-%02i-%02i %02i:%02i:%02i', datenum[1], datenum[2], datenum[3], datenum[4], datenum[5], datenum[6] ) elseif datecode == 'YMDHM' then timeStamp = string.format('%04i-%02i-%02i %02i:%02i', datenum[1], datenum[2], datenum[3], datenum[4], datenum[5] ) elseif datecode:sub(1,3)=='YMD' then timeStamp = string.format('%04i-%02i-%02i', datenum[1], datenum[2], datenum[3] ) datecode = 'YMD' -- 'YMD', 'YMDHMS' and 'YMDHM' are the only supported format starting with 'YMD'. All others will be converted to 'YMD' elseif datecode == 'YM' then timeStamp = string.format('%04i-%02i', datenum[1], datenum[2] ) elseif datecode:sub(1,1)=='Y' then timeStamp = string.format('%04i', datenum[1] ) datecode = 'Y' elseif datecode == 'M' then timeStamp = string.format('%04i-%02i-%02i', 2000, datenum[2], 1 ) elseif datecode == 'MD' then timeStamp = string.format('%04i-%02i-%02i', 2000, datenum[2], datenum[3] ) else timeStamp = nil -- format not supported end return timeStamp, datecode end --------------------------------------------------------------------------------------- -- trim leading zeros in years prior to year 1000 -- INPUT: -- * datestr - translated date string -- * lang - language of translation -- OUTPUT: -- * datestr - updated date string local function trimYear(datestr, year, lang) local yearStr0, yearStr1, yearStr2, zeroStr yearStr0 = string.format('%04i', year ) -- 4 digit year in standard form "0123" yearStr1 = mw.language.new(lang):formatDate( 'Y', yearStr0) -- same as calling {{#time}} parser function --yearStr1 = mw.getCurrentFrame():callParserFunction( "#time", { 'Y', yearStr0, lang } ) -- translate to a language if yearStr0==yearStr1 then -- most of languages use standard form of year yearStr2 = tostring(year) else -- some languages use different characters for numbers yearStr2 = yearStr1 zeroStr = mw.ustring.sub(yearStr1,1,1) -- get "0" in whatever language for i=1,3 do -- trim leading zeros if mw.ustring.sub(yearStr2,1,1)==zeroStr then yearStr2 = mw.ustring.sub(yearStr2, 2, 5-i) else break end end end return string.gsub(datestr, yearStr1, yearStr2 ) -- in datestr replace long year with trimmed one end --------------------------------------------------------------------------------------- -- Look up proper format string to be passed to {{#time}} parser function -- INPUTS: -- * datecode: YMDHMS, YMDHM, YMD, YM, MD, Y, or M -- * day : Number between 1 and 31 (not needed for most languages) -- * lang : language -- OUTPUT: -- * dFormat : input to {{#time}} function local function getDateFormat(datecode, day, lang) local function parseFormat(dFormat, day) if dFormat:find('default') and #dFormat>10 then -- special (and messy) case of dFormat code depending on a day number -- then json contains a string with more json containing "default" field and 2 digit day keys -- if desired day is not in that json than use "default" case dFormat = dFormat:gsub('”','"') -- change fancy double quote to a straight one, used for json marking local D = mw.text.jsonDecode( dFormat ) --com = mw.dumpObject(D) day = string.format('d%02i',day) -- create day key dFormat = D[day] or D.default dFormat = dFormat:gsub("'", '"') -- change single quote to a double quote, used for {{#time}} marking end return dFormat end local T = {} local tab = mw.ext.data.get('DateI18n.tab', lang) for _, row in pairs(tab.data) do -- convert the output into a dictionary table local id, _, msg = unpack(row) T[id] = msg end local dFormat = T[datecode] if dFormat=='default' and (datecode=='YMDHMS' or datecode=='YMDHM') then -- for most languages adding hour:minute:second is done by adding ", HH:MM:SS to the -- day precission date, those languages are skipped in DateI18n.tab and default to -- English which stores word "default" dFormat = parseFormat(T['YMD'], day).. ', H:i' if datecode=='YMDHMS' then dFormat = dFormat .. ':s' end else dFormat = parseFormat(dFormat, day) end return dFormat end --------------------------------------------------------------------------------------- -- Look up proper format string to be passed to {{#time}} parser function -- INPUTS: -- * month : month number -- * case : gramatic case abbriviation, like "ins", "loc" -- * lang : language -- OUTPUT: -- * dFormat : input to {{#time}} function local function MonthCase(month, case, lang) local T = {{},{},{},{},{},{},{},{},{},{},{},{}} local tab = mw.ext.data.get('I18n/MonthCases.tab', lang) for _, row in pairs(tab.data) do local mth, cs, msg = unpack(row) T[mth][cs] = msg end return T[month][case] end -- ================================================== -- === External functions =========================== -- ================================================== local p = {} --[[ ======================================================================================== Date This function is the core part of the ISOdate template. Usage: local Date = require('Module:DateI18n')._Date local dateStr = Date({2020, 12, 30, 12, 20, 11}, lang) Parameters: * {year,month,day,hour,minute,second, tzhour, tzmin}: broken down date-time component strings or numbers tzhour, tzmin are timezone offsets from UTC, hours and minutes * lang: The language to display it in * case: Language format (genitive, etc.) for some languages * class: CSS class for the <time> node, use "" for no metadata at all ]] function p._Date(datevec, lang, case, class, trim_year) -- make sure inputs are in the right format if not lang or not mw.language.isValidCode( lang ) then lang = mw.getCurrentFrame():callParserFunction( "int", "lang" ) -- get user's chosen language end if lang == 'be-tarsk' then lang = 'be-x-old' end -- process datevec and extract timeStamp and datecode strings as well as numeric datenum array local datecode, datenum = parserDatevec(datevec) local year, month, day = datenum[1], datenum[2], datenum[3] local timeStamp, datecode = getTimestamp(datecode, datenum) if not timeStamp then -- something went wrong in parserDatevec return '' end -- Commons [[Data:DateI18n.tab]] page stores prefered formats for diferent -- languages and datecodes (specifying year-month-day or just year of month-day, etc) -- Look up country specific format input to {{#time}} function local dFormat = getDateFormat(datecode, day, lang) -- By default the gramatical case is not specified (case=='') allowing the format to be specified -- in [[Data:DateI18n.tab]]. You can overwrite the default grammatical case of the month by -- specifying "case" variable. This is needed mostly by Slavic languages to create more complex -- phrases as it is done in [[c:Module:Complex date]] case = case or '' if (lang=='qu' or lang=='qug') and (case=='nom') then -- Special case related to Quechua and Kichwa languages. The form in the I18n is -- Genitive case with suffix "pi" added to month names provided by {#time}} -- in Nominative case that "pi" should be removed -- see https://commons.wikimedia.org/wiki/Template_talk:Date#Quechua from 2014 dFormat = dFormat:gsub('F"pi"', 'F') elseif (case=='gen') then dFormat = strReplace(dFormat, "F", "xg") elseif (case=='nom') then dFormat = strReplace(dFormat, "xg", "F") elseif (case ~= '') then -- see is page [[Data:I18n/MonthCases.tab]] on Commons have name of the month -- in specific gramatic case in desired language. If we have it than replace -- "F" and xg" in dFormat local monthMsg = MonthCase(month, case, lang) if monthMsg and monthMsg ~= '' then -- make sure it exists dFormat = strReplace(dFormat, 'F', '"'..monthMsg..'"') -- replace default month with month name we already looked up dFormat = strReplace(dFormat, 'xg', '"'..monthMsg..'"') end end -- Translate the date using specified format -- See https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#mw.language:formatDate and -- https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions##time for explanation of the format local datestr = mw.language.new(lang):formatDate( dFormat, timeStamp) -- same as using {{#time}} parser function -- Special case related to Thai solar calendar: prior to 1940 new-year was at different -- time of year, so just year (datecode=='Y') is ambiguous and is replaced by "YYYY or YYYY" phrase if lang=='th' and datecode=='Y' and year<=1940 then datestr = string.format('%04i หรือ %04i', year+542, year+543 ) end -- If year<1000 than either keep the date padded to the length of 4 digits or trim it -- decide if the year will stay padded with zeros (for years in 0-999 range) if year and year<1000 then if type(trim_year)=='nil' then trim_year = '100-999' end local trim = yesno(trim_year,nil) -- convert to boolean if trim==nil and type(trim_year)=='string' then -- if "trim_year" not a simple True/False than it is range of dates -- for example '100-999' means to pad one and 2 digit years to be 4 digit long, while keeping 3 digit years as is local YMin, YMax = trim_year:match( '(%d+)-(%d+)' ) trim = (YMin~=nil and year>=tonumber(YMin) and year<=tonumber(YMax)) end if trim==true then datestr = trimYear(datestr, year, lang) -- in datestr replace long year with trimmed one end end -- append timezone if present if datenum[7] and (datecode == 'YMDHMS' or datecode == 'YMDHM') then -- use {{#time}} parser function to create timezone string, so that we use correct character set local sign = (datenum[7]<0) and '−' or '+' timeStamp = string.format("2000-01-01 %02i:%02i:00", math.abs(datenum[7]), datenum[8] or 0) local timezone = mw.language.new(lang):formatDate( 'H:i', timeStamp) -- same as using {{#time}} parser function datestr = string.format("%s %s%s", datestr, sign, timezone ) end -- html formating and tagging of date string if class and class ~= '' and datecode~='M' and datecode~='MD'then local DateHtmlTags = '<span style="white-space:nowrap"><time class="%s" datetime="%s">%s</time></span>' datestr = DateHtmlTags:format(class, timeStamp, datestr) end return datestr end --[[ ======================================================================================== Date This function is the core part of the ISOdate template. Usage: {{#invoke:DateI18n|Date|year=|month=|day=|hour=|minute=|second=|tzhour=|tzmin=|lang=en}} Parameters: * year, month, day, hour, minute, second: broken down date-time component strings * tzhour, tzmin: timezone offset from UTC, hours and minutes * lang: The language to display it in * case: Language format (genitive, etc.) for some languages * class: CSS class for the <time> node, use "" for no metadata at all ]] function p.Date(frame) local args = {} for name, value in pairs( frame.args ) do name = string.gsub( string.lower(name), ' ', '_') args[name] = value end return p._Date( { args.year, args.month, args.day, args.hour, args.minute, args.second, args.tzhour, args.tzmin }, args.lang, -- language args.case, -- allows to specify grammatical case for the month for languages that use them args.class or 'dtstart', -- allows to set the html class of the time node where the date is included. This is useful for microformats. args.trim_year or '100-999' -- by default pad one and 2 digit years to be 4 digit long, while keeping 3 digit years as is ) end return p lcwmkh5p6iicle9dw7qzn6d8j6xkduv 803624 803623 2026-07-30T00:19:47Z SM7 3953 11 revisions imported from [[:en:Module:DateI18n]] 803622 Scribunto text/plain --[[ __ __ _ _ ____ _ ___ _ ___ | \/ | ___ __| |_ _| | ___ _| _ \ __ _| |_ ___|_ _/ |( _ ) _ __ | |\/| |/ _ \ / _` | | | | |/ _ (_) | | |/ _` | __/ _ \| || |/ _ \| '_ \ | | | | (_) | (_| | |_| | | __/_| |_| | (_| | || __/| || | (_) | | | | |_| |_|\___/ \__,_|\__,_|_|\___(_)____/ \__,_|\__\___|___|_|\___/|_| |_| This module is intended for processing of date strings. Please do not modify this code without applying the changes first at Module:Date/sandbox and testing at Module:Date/sandbox/testcases and Module talk:Date/sandbox/testcases. Authors and maintainers: * User:Parent5446 - original version of the function mimicking template:ISOdate * User:Jarekt - original version of the functions mimicking template:Date ]] require('strict') -- ================================================== -- === Internal functions =========================== -- ================================================== -- Function allowing for consistent treatment of boolean-like wikitext input. -- It works similarly to Module:Yesno local function yesno(val, default) if type(val) == 'boolean' then return val elseif type(val) == 'number' then if val==1 then return true elseif val==0 then return false end elseif type(val) == 'string' then val = mw.ustring.lower(val) -- put in lower case if val == 'no' or val == 'n' or val == 'false' or tonumber(val) == 0 then return false elseif val == 'yes' or val == 'y' or val == 'true' or tonumber(val) == 1 then return true end end return default end --------------------------------------------------------------------------------------- -- String replacement that ignores part of the string in "..." local function strReplace(String, old, new) if String:find('"') then local T={} for i, str in ipairs(mw.text.split( String, '"', true )) do if i%2==1 then str = str:gsub(old, new) end table.insert(T, str) end return table.concat(T,'"') else return String:gsub(old, new) end end --------------------------------------------------------------------------------------- -- process datevec -- INPUT: -- * datevec - Array of {year,month,day,hour,minute,second, tzhour, tzmin} containing broken -- down date-time component strings or numbers -- OUTPUT: -- * datecode - a code specifying content of the array where Y' is year, 'M' is month, -- 'D' is day, 'H' is hour, 'M' minute, 'S' is second. output has to be one of YMDHMS, YMDHM, YMD, YM, MD, Y -- * datenum - same array but holding only numbers or nuls local function parserDatevec(datevec) -- if month is not a number than check if it is a month name in project's language local month = datevec[2] if month and month~='' and not tonumber(month) then datevec[2] = mw.getContentLanguage():formatDate( "n", month) end -- create datecode based on which variables are provided and check for out-of-bound values local maxval = {nil, 12, 31, 23, 59, 59, 23, 59} -- max values for year, month, ... local minval = {nil, 1, 1, 0, 0, 0, -23, 0} -- min values for year, month, ... local c = {'Y', 'M', 'D', 'H', 'M', 'S', '', ''} local datecode = '' -- a string signifying which combination of variables was provided local datenum = {} -- date-time encoded as a vector = [year, month, ... , second] for i = 1,8 do datenum[i] = tonumber(datevec[i]) if datenum[i] and (i==1 or (datenum[i]>=minval[i] and datenum[i]<=maxval[i])) then datecode = datecode .. c[i] end end return datecode, datenum end --------------------------------------------------------------------------------------- -- process datevec -- INPUT: -- * datecode - a code specifying content of the array where Y' is year, 'M' is month, -- 'D' is day, 'H' is hour, 'M' minute, 'S' is second. output has to be one of YMDHMS, YMDHM, YMD, YM, MD, Y -- * datenum - Array of {year,month,day,hour,minute,second, tzhour, tzmin} as numbers or nuls -- OUTPUT: -- * timeStamp - date string in the format taken by mw.language:formatDate lua function and {{#time}} perser function -- https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#mw.language:formatDate -- https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions#.23time -- * datecode - with possible corrections local function getTimestamp(datecode, datenum) -- create time stamp string (for example 2000-02-20 02:20:20) based on which variables were provided local timeStamp if datecode == 'YMDHMS' then timeStamp = string.format('%04i-%02i-%02i %02i:%02i:%02i', datenum[1], datenum[2], datenum[3], datenum[4], datenum[5], datenum[6] ) elseif datecode == 'YMDHM' then timeStamp = string.format('%04i-%02i-%02i %02i:%02i', datenum[1], datenum[2], datenum[3], datenum[4], datenum[5] ) elseif datecode:sub(1,3)=='YMD' then timeStamp = string.format('%04i-%02i-%02i', datenum[1], datenum[2], datenum[3] ) datecode = 'YMD' -- 'YMD', 'YMDHMS' and 'YMDHM' are the only supported format starting with 'YMD'. All others will be converted to 'YMD' elseif datecode == 'YM' then timeStamp = string.format('%04i-%02i', datenum[1], datenum[2] ) elseif datecode:sub(1,1)=='Y' then timeStamp = string.format('%04i', datenum[1] ) datecode = 'Y' elseif datecode == 'M' then timeStamp = string.format('%04i-%02i-%02i', 2000, datenum[2], 1 ) elseif datecode == 'MD' then timeStamp = string.format('%04i-%02i-%02i', 2000, datenum[2], datenum[3] ) else timeStamp = nil -- format not supported end return timeStamp, datecode end --------------------------------------------------------------------------------------- -- trim leading zeros in years prior to year 1000 -- INPUT: -- * datestr - translated date string -- * lang - language of translation -- OUTPUT: -- * datestr - updated date string local function trimYear(datestr, year, lang) local yearStr0, yearStr1, yearStr2, zeroStr yearStr0 = string.format('%04i', year ) -- 4 digit year in standard form "0123" yearStr1 = mw.language.new(lang):formatDate( 'Y', yearStr0) -- same as calling {{#time}} parser function --yearStr1 = mw.getCurrentFrame():callParserFunction( "#time", { 'Y', yearStr0, lang } ) -- translate to a language if yearStr0==yearStr1 then -- most of languages use standard form of year yearStr2 = tostring(year) else -- some languages use different characters for numbers yearStr2 = yearStr1 zeroStr = mw.ustring.sub(yearStr1,1,1) -- get "0" in whatever language for i=1,3 do -- trim leading zeros if mw.ustring.sub(yearStr2,1,1)==zeroStr then yearStr2 = mw.ustring.sub(yearStr2, 2, 5-i) else break end end end return string.gsub(datestr, yearStr1, yearStr2 ) -- in datestr replace long year with trimmed one end --------------------------------------------------------------------------------------- -- Look up proper format string to be passed to {{#time}} parser function -- INPUTS: -- * datecode: YMDHMS, YMDHM, YMD, YM, MD, Y, or M -- * day : Number between 1 and 31 (not needed for most languages) -- * lang : language -- OUTPUT: -- * dFormat : input to {{#time}} function local function getDateFormat(datecode, day, lang) local function parseFormat(dFormat, day) if dFormat:find('default') and #dFormat>10 then -- special (and messy) case of dFormat code depending on a day number -- then json contains a string with more json containing "default" field and 2 digit day keys -- if desired day is not in that json than use "default" case dFormat = dFormat:gsub('”','"') -- change fancy double quote to a straight one, used for json marking local D = mw.text.jsonDecode( dFormat ) --com = mw.dumpObject(D) day = string.format('d%02i',day) -- create day key dFormat = D[day] or D.default dFormat = dFormat:gsub("'", '"') -- change single quote to a double quote, used for {{#time}} marking end return dFormat end local T = {} local tab = mw.ext.data.get('DateI18n.tab', lang) for _, row in pairs(tab.data) do -- convert the output into a dictionary table local id, _, msg = unpack(row) T[id] = msg end local dFormat = T[datecode] if dFormat=='default' and (datecode=='YMDHMS' or datecode=='YMDHM') then -- for most languages adding hour:minute:second is done by adding ", HH:MM:SS to the -- day precission date, those languages are skipped in DateI18n.tab and default to -- English which stores word "default" dFormat = parseFormat(T['YMD'], day).. ', H:i' if datecode=='YMDHMS' then dFormat = dFormat .. ':s' end else dFormat = parseFormat(dFormat, day) end return dFormat end --------------------------------------------------------------------------------------- -- Look up proper format string to be passed to {{#time}} parser function -- INPUTS: -- * month : month number -- * case : gramatic case abbriviation, like "ins", "loc" -- * lang : language -- OUTPUT: -- * dFormat : input to {{#time}} function local function MonthCase(month, case, lang) local T = {{},{},{},{},{},{},{},{},{},{},{},{}} local tab = mw.ext.data.get('I18n/MonthCases.tab', lang) for _, row in pairs(tab.data) do local mth, cs, msg = unpack(row) T[mth][cs] = msg end return T[month][case] end -- ================================================== -- === External functions =========================== -- ================================================== local p = {} --[[ ======================================================================================== Date This function is the core part of the ISOdate template. Usage: local Date = require('Module:DateI18n')._Date local dateStr = Date({2020, 12, 30, 12, 20, 11}, lang) Parameters: * {year,month,day,hour,minute,second, tzhour, tzmin}: broken down date-time component strings or numbers tzhour, tzmin are timezone offsets from UTC, hours and minutes * lang: The language to display it in * case: Language format (genitive, etc.) for some languages * class: CSS class for the <time> node, use "" for no metadata at all ]] function p._Date(datevec, lang, case, class, trim_year) -- make sure inputs are in the right format if not lang or not mw.language.isValidCode( lang ) then lang = mw.getCurrentFrame():callParserFunction( "int", "lang" ) -- get user's chosen language end if lang == 'be-tarsk' then lang = 'be-x-old' end -- process datevec and extract timeStamp and datecode strings as well as numeric datenum array local datecode, datenum = parserDatevec(datevec) local year, month, day = datenum[1], datenum[2], datenum[3] local timeStamp, datecode = getTimestamp(datecode, datenum) if not timeStamp then -- something went wrong in parserDatevec return '' end -- Commons [[Data:DateI18n.tab]] page stores prefered formats for diferent -- languages and datecodes (specifying year-month-day or just year of month-day, etc) -- Look up country specific format input to {{#time}} function local dFormat = getDateFormat(datecode, day, lang) -- By default the gramatical case is not specified (case=='') allowing the format to be specified -- in [[Data:DateI18n.tab]]. You can overwrite the default grammatical case of the month by -- specifying "case" variable. This is needed mostly by Slavic languages to create more complex -- phrases as it is done in [[c:Module:Complex date]] case = case or '' if (lang=='qu' or lang=='qug') and (case=='nom') then -- Special case related to Quechua and Kichwa languages. The form in the I18n is -- Genitive case with suffix "pi" added to month names provided by {#time}} -- in Nominative case that "pi" should be removed -- see https://commons.wikimedia.org/wiki/Template_talk:Date#Quechua from 2014 dFormat = dFormat:gsub('F"pi"', 'F') elseif (case=='gen') then dFormat = strReplace(dFormat, "F", "xg") elseif (case=='nom') then dFormat = strReplace(dFormat, "xg", "F") elseif (case ~= '') then -- see is page [[Data:I18n/MonthCases.tab]] on Commons have name of the month -- in specific gramatic case in desired language. If we have it than replace -- "F" and xg" in dFormat local monthMsg = MonthCase(month, case, lang) if monthMsg and monthMsg ~= '' then -- make sure it exists dFormat = strReplace(dFormat, 'F', '"'..monthMsg..'"') -- replace default month with month name we already looked up dFormat = strReplace(dFormat, 'xg', '"'..monthMsg..'"') end end -- Translate the date using specified format -- See https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#mw.language:formatDate and -- https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions##time for explanation of the format local datestr = mw.language.new(lang):formatDate( dFormat, timeStamp) -- same as using {{#time}} parser function -- Special case related to Thai solar calendar: prior to 1940 new-year was at different -- time of year, so just year (datecode=='Y') is ambiguous and is replaced by "YYYY or YYYY" phrase if lang=='th' and datecode=='Y' and year<=1940 then datestr = string.format('%04i หรือ %04i', year+542, year+543 ) end -- If year<1000 than either keep the date padded to the length of 4 digits or trim it -- decide if the year will stay padded with zeros (for years in 0-999 range) if year and year<1000 then if type(trim_year)=='nil' then trim_year = '100-999' end local trim = yesno(trim_year,nil) -- convert to boolean if trim==nil and type(trim_year)=='string' then -- if "trim_year" not a simple True/False than it is range of dates -- for example '100-999' means to pad one and 2 digit years to be 4 digit long, while keeping 3 digit years as is local YMin, YMax = trim_year:match( '(%d+)-(%d+)' ) trim = (YMin~=nil and year>=tonumber(YMin) and year<=tonumber(YMax)) end if trim==true then datestr = trimYear(datestr, year, lang) -- in datestr replace long year with trimmed one end end -- append timezone if present if datenum[7] and (datecode == 'YMDHMS' or datecode == 'YMDHM') then -- use {{#time}} parser function to create timezone string, so that we use correct character set local sign = (datenum[7]<0) and '−' or '+' timeStamp = string.format("2000-01-01 %02i:%02i:00", math.abs(datenum[7]), datenum[8] or 0) local timezone = mw.language.new(lang):formatDate( 'H:i', timeStamp) -- same as using {{#time}} parser function datestr = string.format("%s %s%s", datestr, sign, timezone ) end -- html formating and tagging of date string if class and class ~= '' and datecode~='M' and datecode~='MD'then local DateHtmlTags = '<span style="white-space:nowrap"><time class="%s" datetime="%s">%s</time></span>' datestr = DateHtmlTags:format(class, timeStamp, datestr) end return datestr end --[[ ======================================================================================== Date This function is the core part of the ISOdate template. Usage: {{#invoke:DateI18n|Date|year=|month=|day=|hour=|minute=|second=|tzhour=|tzmin=|lang=en}} Parameters: * year, month, day, hour, minute, second: broken down date-time component strings * tzhour, tzmin: timezone offset from UTC, hours and minutes * lang: The language to display it in * case: Language format (genitive, etc.) for some languages * class: CSS class for the <time> node, use "" for no metadata at all ]] function p.Date(frame) local args = {} for name, value in pairs( frame.args ) do name = string.gsub( string.lower(name), ' ', '_') args[name] = value end return p._Date( { args.year, args.month, args.day, args.hour, args.minute, args.second, args.tzhour, args.tzmin }, args.lang, -- language args.case, -- allows to specify grammatical case for the month for languages that use them args.class or 'dtstart', -- allows to set the html class of the time node where the date is included. This is useful for microformats. args.trim_year or '100-999' -- by default pad one and 2 digit years to be 4 digit long, while keeping 3 digit years as is ) end return p lcwmkh5p6iicle9dw7qzn6d8j6xkduv तिलंगी 0 69954 803323 762645 2026-07-29T16:20:53Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803323 wikitext text/x-wiki [[File:Kitesflying.jpg|right|thumb|alt=आसमान में उड़त कई किसिम के पतंगा|किसिम किसिम के पतंगा]] '''तिलंगी''' भा '''पतंगा''' एगो अइसन जीनिस चाहे वस्तु हऽ जेकरा सुता के मदद से उड़ावल जाला। तिलंगी एगो पाँख नियन सतह के बनल होखेला जेकरा मे एगो सुता बन्हल रहेला। कोनो तिलंगी तबे उड़ेले जब हावा के दाबाव सतह के उपरा कम आ नीचवा बेसी होला।<ref>{{cite web |url=http://www.grc.nasa.gov/WWW/K-12/airplane/guided.htm |title=Beginner's Guide to Aeronautics |publisher=NASA |accessdate=2012-10-03 |archive-date=2015-03-25 |archive-url=https://web.archive.org/web/20150325054529/http://www.grc.nasa.gov/WWW/K-12/airplane/guided.htm |url-status=dead }}</ref> हावा जवना देने बहेला तवना देने तिलांगीयो जाले। एहिसे तिलंगी पऽ दुगो [[बर]] लागेला, एगो ऊपर देने आ एगो सोझा, एह दुनो बर के चलते जवन कुलि परिनाम बर तिलंगी पऽ लागेला तवना के सुता मे उत्त्पन भइल [[तान (भओतिकी)|तान]] सम्हार लेवेला। द्रव्यन के सिधांत सभ पनियो प लागेला एहिसे तिलंगी के परयोग पनियो मे कइल जा सकेला बाकिर आजकाल ई परचलन मे नइखे।<ref>{{cite web|url=http://2lo.de/kitedive/index.htm |title=Underwater kiting |publisher=2lo.de |date= |accessdate=2012-10-03}}</ref><ref>{{cite web|url=http://www.google.com/patents?id=xldbAAAAEBAJ |title=Hydro kite angling device Jason C. Hubbart |date= |accessdate=2012-10-03}}</ref> ==संदर्भ== {{reflist}} [[श्रेणी:खेलौना]] {{Game-stub}} 2ekytvv8zv5x69em0dk85tqgldh3yr1 समय अवकलन 0 69992 803356 658237 2026-07-29T16:23:13Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803356 wikitext text/x-wiki '''समय अवकलन''' [[समय]] के सपेक्ष मे केवनो [[फलन]] के [[अवकलन]] होखेला। ==चिन्ह== समय अवकलन के लउकावे ला कय गो चिन्ह के परयोग कइल जाला जेहमे सभे से सरल लेबनीज के चिन्ह हऽ: :<math>\frac {dx} {dt}</math> भौतिकी मे एगो आउर चिन्ह के परयोग होखेला जेवना मे [[फलन]] के ऊपर एगो बिंदू लगाई के होकर समय अवकलन के लऊकावल जाला आ हेकरा न्यूटन के चिन्ह कहल जाला: :<math>\dot{x}</math> उंच समय अवकलनो के परयोग होखेला। समय के सापेक्ष मे [[दोसरका अवकलन]] के अइसे लिखल जाला: :<math>\frac {d^2x} {dt^2}</math> जेकर छोटहन रूप <math>\ddot{x}</math> हऽ। कोनो सादिक जइसे: :<math> \vec V = \left[ v_1,\ v_2,\ v_3, \cdots \right] \ , </math> के समय अवकलन के होकर घटक सभ के समय अवकलन से बने वला सदिक होखेला: :<math> \frac {d \vec V } {dt} = \left[ \frac{ d v_1 }{dt},\frac {d v_2 }{dt},\frac {d v_3 }{dt}, \cdots \right] \ . </math> == भओतिकी मे परयोग== समय अवकलन [[भौतिकी]] के एगो चिन्हल सिधांत हटे, कोनो बदलत [[पोजिसन (वेक्टर)|स्तिथि सदीक]] <math>x\,</math> ला होकर समय अवकलन <math>\dot{x}</math> [[वेग]] आ दोसरका समय अवकलन <math>\ddot{x}</math> परवेग होला। कबो कबो आऊरो ऊंच अवकलन के परयोग होखेला: समय के सापेक्ष मे स्तिथि के तीसरका अवकलन के [[जरक]] कहल जाला। [[गति के लेखाचित्र आउर अवकलन]] देखीं। भओतिकी मे बहुते बेसी समीकरन सभ मे समय के सापेक्ष पहिलका चाहे दोसरका अवकलन होला।भौतिकी के ढेरे मत्रक एक दोसरा के समय अवकलन होखेलऽसन: * [[बर]] [[वेगमान]] के समय अवकलन बा। * [[तागत (भओतिकी)|तागत]] [[ऊर्जा]] के समय अवकलन बा। * [[बिद्युत धार]] [[बिद्यूत भार]] के समय अवकलन बा। आ अइसन अइसन ढेरे मात्रक हवऽसन। भओतिकी मे सदिक सभ के समय अवकलन कय हाली सोझा आवेला जइसे [[वेग]]। अइसन अवकलन मे सदिक के मान अउरी दिसा दून्नो समय प नीरभर होखेला। ===उदाहरन : बृतिय गती=== [[Image:polar rectangular.svg|thumb|300px|कारटीसियन निर्देशांक (''x'',''y'') आऊर ध्रुवीय निर्देशांक (''r'',''θ'') मे संबंध।]] उदाहरन बदे, मान लिहीं की एगो कन गोल बांट प चलऽता। होकर स्तिथि <math>r=x\hat{\imath}+y\hat{\jmath}</math> सदिक से लउकावल जाता, तऽ ओह कन के ध्रुवीय स्तिथि निर्देशांक हई होई: :<math>\begin{align} x &= r \cos(\theta) \\ y &= r \sin(\theta) \end{align}</math> उदाहरन बदे, मान लिहीं की {{Nowrap|1=''θ'' = ''t''}}, तऽ कोनो छन स्तिथि होई: :<math>\mathbf{r}(t) = r\cos(t)\hat{\imath}+r\sin(t)\hat{\jmath}</math> ई रूप देखावऽता की '''''r'''''(''t'') से लउकावल गति एगो '''r''' त्रीज्या के बृत प होखऽता। गती के इहो रूप ले [[वेग]] निकारल जा सकेला, जेवन की एह रूप के समय अवकलन होई। कोनो सदिक के समय अवकलन होकर घटक सभ के समय अवकलन ले बनल सदिक होखेला। एहिसे एहिजा वेग सदिक हई होई: :<math> \begin{align} \mathbf{v}(t) = \frac {d\,\mathbf{r}(t) }{dt} &= r \left[\frac{d\, \cos(t)}{dt}, \frac{d\, \sin(t)}{dt} \right] \\ &= r\ [ -\sin(t),\ \cos(t)] \\ &= [-y (t), x(t)]. \end{align}</math> एहिसे बृतिय गति मे भले स्तिथि ना बदले बाकिर वेग सुन्ना ना होखेला। वेग अउरी स्तिथी सादिक महे ९०° के कोन बनेला, जेकरा [[अदिक गुना]] ले सिध कइल जाई सकेला। :<math>\mathbf{v} \cdot \mathbf{r} = [-y, x] \cdot [x, y] = -yx + xy = 0\, . </math> तब [[परवेग]] वेग के समय अवकलन होई: :<math>\mathbf{a}(t) = \frac {d\, \mathbf{v}(t)}{dt} = [-x(t), -y(t)] = -\mathbf{r}(t)\, .</math> एह परवेग सदिक के दिसा अक्ष देने होला जेकरा [[केंद्रक परवेग]] कहल जाला। {{Math-stub}} sng3b3fnd1kdw1sr2347muwl77xbvvy अवकलन 0 70032 803300 658246 2026-07-29T16:06:52Z SM7Bot 7808 /* top */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803300 wikitext text/x-wiki '''अवकलन''' कोनो [[असल चरक वला फलन]] के मान मे होकर [[चरक]] मे बदलाव के साथे आवे वला बदलाव के भागा होखेला। अवकलन [[कलन]] के एगो जरूरी अउजार हटे। उदाहरन बदे, स्थिति सदिक के [[समय]] के सपेक्ष् अवकलन [[वेग]] होला: हेकरा ले ई नपाला की कोनो चीझ कतना हाली हाली आपन अस्थान मे परीबरतन करऽता। खाली एगो चरक वला फलन के अवकलन कोनो निश्चित बिंदु प [[स्पर्श पाई]] के प्रवनता चाहे स्लोप देवेला। अवकलन सभ के [[एक से बेसी असल चरक वला फलन]] सभो जोरे परयोग कइल जा सकेला। [[श्रेणी:गणित]] {{Math-stub}} 0d2tt9zctrtmlv8yazd30qjmtkcq3ss गति 0 72111 803321 752785 2026-07-29T16:20:45Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803321 wikitext text/x-wiki [[भौतिकी]] मे '''गति''', समय जोर कोनो बस्तु के स्थान मे परीवरतन के कहल जाला। गनित मे गति के [[दुरत्व]], [[वेग]], [[परवेग]] के भेस मे लउकावल जाला। कोनो बस्तू के गति के निरिक्षण होकरा जोरे एगो [[परसंग परनाली]] लगाइके कइल जाला। जदि कोनो परसंग परनाली ले देखला प बस्तू के स्थान मे परिवरतन नइखे होखत हेकर माने हई हऽ की ऊ बस्तु '''असथिर''' बा। बिना परसंग परनाली के गति के बिबरन ना दिहल जा सकेला। एहिसे ब्राम्हण के सभे बस्तु के गतिमान मानल जा सकेला।<ref name=Tyson>{{cite book|last=Tyson|first=Neil de Grasse|author2=Charles Tsun-Chu Liu|author3=Robert Irion|title=The universe : at home in the cosmos|year=2000|publisher=[[National Academy Press]]|location=Washington, DC|isbn=978-0-309-06488-0|url-access=registration|url=https://archive.org/details/oneuniverse00neil}}</ref> कोनो चीझा के स्थान, चाहे अस्तिथी मे बदलाव के गति कहल जा सकेला। उदाहरन बदे, केहू [[तरंग]] चाहे कवाँटम कन के गति बदे बतिया सकेला। गति के नापे वला मुख्य मात्रा [[वेगमान]] हटे। बस्तु के वेगमान होकर [[भार]] आउर [[वेग]] संगे अधिकावेला। अइसन बेबसथा जेहमे बहरा से कोनो [[बर]] ना लागेला, होकर वेगमान समय संगे ना बदलेला, एहिसे कोनो बस्तु के गति बिना ओहपऽ बर लगवले नइखे बदलल जा सकत। == गति के नियम == {{Main|बरबिद्या}} भौतिकी मे, भारी बस्तुअन के गति के बरनन दुगो गति के [[बैज्ञानिक नियम|नियम]] से होखेला। [[ब्रह्मांड]] के सभ बड़हन बस्तुअन के गति के बरनन [[क्लासिकल मैकेनिक्स]] के परयोग के होखेला आ छोटहन कन सभे के [[क्वांटम मैकेनिक्स]] के परयोग के होखेला। गति के तीनिगो नियम [[आइजक न्युटन|न्यूटन]] दिहले बाड़न। {| class="wikitable" |'''पहिलका नियम''': |कोनो [[जड़ परसंग परनालि]] मे, कोनो बस्तु तबले '''अस्थिर''' रहेला चाहे एक्के [[वेग]] से चलेला, जबले ओहपऽ बहरा से केवनो [[बर]] ना लगावल जाला। |- |'''दूसरका नीयम''': |कोनो [[जड़ परसंग परनालि]] मे, बस्तु प लगावल सउँसे [[बर]] सदिकन के जोड़ ओह बस्तु के [[भार]] m आउर [[परवेग]] '''a''' के गूना होखेला।: '''F''' = ''m'''''a'''. |- |'''तीसरका नियम''': |जवन खानी एगो बस्तु दोसरा बस्तु प बर लगवेला, ओही खानी दोसरका बस्तु पहिलका प बराबर बर बिपरित दिसा ने लगवेला। |} === चिरायत बरबिद्या === चिरायत बरबिद्या के परयोग बड़हन कनन के गति बतावे ला कइल जाला। आपन दायरा मे की एकदम सटिक परिनाम देवेला। चिरायत बरबिद्या [[न्यूटन के गति के नियम]] प निरभर रहेला। ई नियम सभ कोनो चीझ के गति आ ओहपे लागत बर के संबंध देखावेलऽ सन। एखिनी के पहिला हाली [[आइजक न्युटन]] जी आपन किताब [[प्रिंसिपिया नेचुरालिस प्रिंसिपिया मेथेमेटीका]] मे लिखले रहनी जेवन 5 जुलाई 1684 के छपल रहे। === सपेक्ष बरबिद्या === आधुनिक गतीबिज्ञान जेवन की [[विद्युत्चुम्बकत्व]] दिया सोध कई के बिकसित भइल बा, ओहमें कुलि वेगन के [[अँजोरिया के गतिवेग]] संगे भागा लगाई के देखावल जाला। हेकरे के [[बिशेस सापेक्षता]] कहल जाला। === क्वांटम मैकेनिक्स === क्वांटम मैकेनिक्स कर छोटहन कन सभ के गति के बुझे जाने बदे परयोग होला। हेकर परयोग [[अती तरलता]] खानी जरूरी आ अबुझ चीजन के बूझे बदे कइल जाला। == मनुस के ना बूझावे वला गातियन के सूची == ब्रह्माण्ड के आउर सभ बस्तुअन खानी हरदम गति मे रहेला बाकिर एह सभ के अलावे कुछु अइसनो अइसनो गति होला जेकरा देखल ना जा सके चाहे देखल कठिन होखेला। === ब्रह्माण्ड === [[काल-अवकाश]] के [[ब्रह्माण्ड के बिस्तार|छितरे]] के कारन ई हऽ की [[ब्रह्मांड]] के हर चीझ एगो रबर बैंड खानी छितरऽता। ई गति अस्पष्ट बा आ भौतिक नइखे। === गैलेक्सी === बहुते बैज्ञानिक ई मानेलन कि [[आकाशगंगा]] ब्रह्माण्ड में घुरऽता जेकर गति 600&nbsp;km प्रति सेकेंड हवे। === सूरूज आ गरहा === सूरूज आकाशगंगा जोरे घुरऽता आ सूरूज के चहुदिसी मय गरहा सभ घुरेलऽसन। === धरती === धरती आपन अक्ष प घुरेले, [[दिन]] आ [[रात]] हेकरा परमानित करेला। धरती सूरजो के चारो देने घूरेले आ एक बरिस मे आपन एक घुमरि पुरावेले। === महादेस === [[प्लेट टेक्टॉनिक्स]] के सिधांत कहेला की [[महादीप]] सभ धरती के सतह प गति मे बाड़ऽसन। === भितारिया देह === मनुस के [[दिल|जियरा]] निरंतर काज करत रहेला। == अँजोर == {{main|अँजोरिया के गतिवेग}} अंजोरा 299,792,458&nbsp;m/s के गति से चलेला, निर्यात मे एहसे तेज कूछुवो ना चले। हेकर संगे अँजोरिया के गति परसंग परनाली प निरभर ना करे। == गति के परकार == * [[सरल आवर्त गति]]: पेंडुलम के गति। == संदर्भ == {{Reflist}} [[श्रेणी:भौतिकी]] {{Physics-stub}} 3wvmibize3sf7hbci2nhi89wwxpeomm बबल छँटाई 0 72199 803322 776983 2026-07-29T16:20:49Z SM7Bot 7808 /* बिधी जोरे उदाहरण */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803322 wikitext text/x-wiki '''बबल छँटाई''' एगो [[छँटाई के बिधी]] हऽ जेवन की सऊँसे लिस्ट मे बेर बेर घूरेला, अगिला चिक्ष के पिछलका से तूलना करेला आ जदि ऊ दुन्नो सही क्रम मे ना रहे तऽ दून्नो के [[अदला-बदली (प्रोगरमींग)|अदला-बदली]] कर देवेला। एहमें तबले लिस्ट भ मे घुरल जाला जबले पूरा लिस्ट के छँटाई ना हो जाला। भले ई बिधि बहुते सरल बा बाकिर बहुते लहे लहे काम करेला आ कतना समस्या सभ ला अप्ययोगिक बा। {{Infobox Algorithm |class=[[Sorting algorithm]] |image=Bubblesort-edited-color.svg| | caption=Static visualization of bubble sort<ref>{{cite web |last=Cortesi |first=Aldo |title=Visualising Sorting Algorithms |url=https://corte.si/posts/code/visualisingsorting/index.html |date=27 April 2007 |accessdate=16 March 2017}}</ref> |data=[[Array data structure|Array]] |time=<math>O(n^2)</math> comparisons, <math>O(n^2)</math> swaps |average-time=<math>O(n^2)</math> comparisons, <math>O(n^2)</math> swaps |best-time=<math>O(n)</math> comparisons, <math>O(1)</math> swaps |space=<math>O(1)</math> auxiliary |optimal=No }} ==बिबेचन== [[File:Bubble-sort-example-300px.gif|300px|thumb|right|बबल छँटाई बीधी के एगो उदाहरन, लिस्ट के पहिला अंग से चालू करीं, अगिला मय जोड़ा के मिलाईं, जदि दुन्नो सही क्रम मे नइखे तऽ दुन्नो के अदला बदली कर दिहीं। लिस्ट मे तबले घुरीं जबले सभ अंग एगो बिसेस क्रम मे नइखे आ जात।]] ===परदरसन=== बबल छँटाई के सभसे खराब औसत समय जटिलता [[बड़ O चिन्हा|O]](n ²) हऽ, जेने n छाँटे वला चीझन के संख्या हऽ। बसी समय लिहला के चलते ई बीधि अप्ययोगिक बा। ===बिधी जोरे उदाहरण=== एगो त्राम चाहे अएरे लिहीं "5 1 4 2 8" आ हेकरा बढ़त क्रम मे छाँटी। हर डेग प '''मोटहन''' अंग के मिलावल जाई। लिस्ट मे तीन हाली घुरे के जाओरत पड़ी। '''पहिलका घुमरी''' :( '''5''' '''1''' 4 2 8 ) &rarr; ( '''1''' '''5''' 4 2 8 ), एहिजा, पहिलका दुगो अंकन के मिलावल जाता आ अदला बदली कइल जाता। एहसे के 5>1 :( 1 '''5''' '''4''' 2 8 ) &rarr; ( 1 '''4''' '''5''' 2 8 ), बदल दिहिन एहसे की 5 > 4 :( 1 4 '''5''' '''2''' 8 ) &rarr; ( 1 4 '''2''' '''5''' 8 ), बदल दिहिं एहसे की 5 > 2 :( 1 4 2 '''5''' '''8''' ) &rarr; ( 1 4 2 '''5''' '''8''' ), अबहिन, ई दुन्नो पहिलहीं क्रम मे बाड़न सन(8 > 5), तऽ एखिनी के अदला बदली कइल जाई ;दूसरका घुमरी :( '''1''' '''4''' 2 5 8 ) &rarr; ( '''1''' '''4''' 2 5 8 ) :( 1 '''4''' '''2''' 5 8 ) &rarr; ( 1 '''2''' '''4''' 5 8 ), बदल दिहिं एहसे की 4 > 2 :( 1 2 '''4''' '''5''' 8 ) &rarr; ( 1 2 '''4''' '''5''' 8 ) :( 1 2 4 '''5''' '''8''' ) &rarr; ( 1 2 4 '''5''' '''8''' ) अबहिन, लिस्ट क्रम मे आ गइल बा, बाकिर बिधी नइखे जानत के क्रम मे बा की ना. हेकरा बदे लिस्ट मे फेर एक हाली घूरे के पड़ी ;तीसरका घूमरी :( '''1''' '''2''' 4 5 8 ) &rarr; ( '''1''' '''2''' 4 5 8 ) :( 1 '''2''' '''4''' 5 8 ) &rarr; ( 1 '''2''' '''4''' 5 8 ) :( 1 2 '''4''' '''5''' 8 ) &rarr; ( 1 2 '''4''' '''5''' 8 ) :( 1 2 4 '''5''' '''8''' ) &rarr; ( 1 2 4 '''5''' '''8''' ) == संदर्भ == {{Commons category}} {{Reflist|33em}} [[श्रेणी:कंप्यूटर बिज्ञान]] {{Comp-sci-stub}} htrmcnmvebiuhm8180zycwrl6zjr6g6 बैज्ञानिक नियम 0 72268 803287 781473 2026-07-29T16:05:52Z SM7Bot 7808 /* top */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803287 wikitext text/x-wiki '''बैज्ञानिक नियम''' ({{Lang|en|scientific law}}) अइसन कहनाम चाहे कथन के कहल जाला जे परयोगन से सिध कइल रहले चाहे कोनो भौतिक घटना के बीबरन दिहेला। [[श्रेणी:बिज्ञान|नियम]] {{Science-stub}} 9fgdcmc5kynaldgv3zh949mly8ldb1y मोस्फेट 0 72298 803298 734413 2026-07-29T16:06:44Z SM7Bot 7808 /* top */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803298 wikitext text/x-wiki [[file:MOSFET Structure.png|thumb|upright=1.6|मोस्फेट, दुआर (G), देह (B), स्त्रोत (S) अउरी निकास (D) देखवात।]] '''मोस्फेट''' एगो [[क्षेत्र परभावित ट्राजिस्टर]] हऽ, जेकरा भिरी एगो गेट होला जेकर [[बिभव]] तंत्र के चालकता बतावेला। बीभव संगे चालकता बदले के एह सकान के परयोग बिद्यूत संकेत के परवर्धन मे कइल जाला। मोस्फेट के अविष्कार इजीप्ट के अभियंता [[मोहम्मद एम आत्तल्लाह]] जी अउरी कोरिआ के अभियांता [[दावों कांग]] जी 1969 मे कइले रहनीं। ई [[रीनानु बिज्ञान]] के नेव हऽ आ इतिहास मे सभसे बेसि बने वला तंत्र बाटे। csn1bkra37ppq0nhwzkzdezg4bzoun1 विद्युत्चुम्बकत्व 0 72302 803288 721497 2026-07-29T16:05:57Z SM7Bot 7808 /* top */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803288 wikitext text/x-wiki '''विद्युत्चुम्बकत्व''' [[भौतिकी]] के एगो डाढ़ ह जेहमे [[बिद्यूतभार]] सभ के बीचे लागे वला [[बर]] प सोध कइल जाला। [[श्रेणी:भौतिकी]] {{Physics-stub}} mz0e6qtm09dw7bc65kx9s26vhs1gqk4 वेक्टर स्पेस 0 72586 803290 721562 2026-07-29T16:06:05Z SM7Bot 7808 /* top */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803290 wikitext text/x-wiki '''सदिक समुह''' मे [[सदिक|सदिकन]] के सञित के धइल जाला आ ओखिनी के जोड़, घटाऊ आ कोनो [[अदिक]] जोरे गुना कइल जाला। अदिक जादे तर कोनो [[सत संख्या]] के लिहल जाला। [[श्रेणी:गणित]] {{Math-stub}} hu21s52ve73pn0yn1japus3r4b73ca3 सीरीज (गणित) 0 72616 803286 721485 2026-07-29T16:05:37Z SM7Bot 7808 /* top */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803286 wikitext text/x-wiki [[गणित]] मे, '''लरी''' के जदि धिल रूप से परिभसित कइल जाए त ई एगो अइसन काज हऽ जेकरा मे अनंत मात्रा सभ के पारा पारी जोड़ल जाला। [[श्रेणी:गणित]] {{Math-stub}} j6skbhrkqdo6hjwpopbwqqtv36131eb बिदेसिया (तमासा) 0 75784 803303 774441 2026-07-29T16:07:04Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803303 wikitext text/x-wiki {{infobox play | name = बिदेसिया | writer = [[भिखारी ठाकुर]] | chorus = | characters = | mute = | setting = | premiere = 1912 | place = | orig_lang = | series = | subject = [[पलायन]], [[महिला सशक्तिकरण]], [[गरीबी]] | genre = [[रंगमंच]] | web = }} '''बिदेसिया''' (कैथी: 𑂥𑂱𑂠𑂵𑂮𑂱𑂨𑂰) चाहे '''बहरा बहार''' [[भिखारी ठाकुर]] के लिखल एगो [[भोजपुरी]] [[तमासा]] हऽ। ई भिखारी ठाकुर के गरीबी, पलायन आ नारी सशक्तिकरण प लिखल नाटक सभ मे से एगो बा।<ref>{{Cite journal|last=Singh|first=Dhananjay|date=September 2018|title=The Image of Women in Folk-Traditions of Migration|url=https://tiss.edu/uploads/files/4._Dhananjay_Singh.pdf|journal=Journal of Migration Affairs|volume=1|pages=55|via=the image of women in Bhojpuri folk- culture/literature of migration is colourful. The women themselves are in a way suffering from migration—whether they have been left behind, or if they have been forced to migrate themselves, or if they have come with her husband to his village as an urhari. The seriousness with which Bhikhari Thakur has dealt with the travails of these women is unparalleled. In his play ‘Bidesiya’ he shows Pyari Sundari’s suffering as the ideal chaste wife waiting for her husband in the village}}</ref> ई नाटक आपन ख्याती के चलते एगो भोजपुरी भाषा के एगो नाट्य शैली बनि गइल, आ दोसरो भाषा मे एह शैली मे तमासा लिखाइल, ओह घड़ी के बिद्वान लोग एकर प्रसिद्धि के तुलना रामायण से कइल बा।<ref>{{Cite book|last=यांग|first=आनंद |title=The Limited Raj: Agrarian Relations in Colonial India, Saran District, 1793-1920|publisher=Univ of California Press|year=2021|isbn=9780520369108}}</ref> एह तमासा मे उनइसवा सताबदी के भोजपुरी समाज मे मेहरारुन के दुरदसा के देखावल गइल बा। एह नाटक के 1917 मे लिखल गइल रहे।<ref>{{Cite book|last=नारायन|first=बद्री|title=Culture and Emotional Economy of Migration|publisher=Taylor and Francis|page=74}}</ref> ==नांव== एकरा पहिलका बेरा कलजुग प्रेम नांव से परकासित कइल गइल रहे, फेर बहरा बहार नांव से छपाईल, बाकिर एकर बिदेसिया नांव के पाट के चलते ई एही नांव से परसिद्ध भइल।<ref>{{Cite book|title=Bhikhari Thakur: Bhojpuri Ke Bharatendu|publisher=Aashu Prakashan|location=Allahabad}}</ref> == संदर्भ == <br /> {{Play-stub}} givroicyrq0i6fabl3r94xvlqooc4dd राजमोहन 0 76172 803333 774904 2026-07-29T16:21:34Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803333 wikitext text/x-wiki {{Infobox musical artist | name = राज मोहन | native_name = | image = | caption = राज मोहन 2019 में | image_size = 250 | background = सोलो गायक | birth_name = राज मोहन<ref>{{cite web|url=https://www.thelallantop.com/videos/interview-of-raj-mohan-a-netherlands-based-bhojpuri-singer-musician-and-lyricist/|title=नीदरलैंड्स में रहे वाला सिंगर राज मोहन ले सुनी बिदेस में गावे वाला भोजपुरी गीत|publisher=thelallantop.com|}}</ref> | birth_date = {{Birth date and age|1964|08|28|df=y}} | birth_place = [[पारामारिबो]], [[सूरीनाम]] | death_date = | genre = पॉप , ग़ज़ल<ref>{{cite web|url=https://navbharattimes.indiatimes.com/metro/mumbai/other-news/raj-mohan-is-making-bhojpuri-songs-popular-in-suriname/amp_articleshow/67220654.cms|title=राज मोहन : सूरीनाम में भोजपुरी गाना के प्रसिद्ध करत बाड़न|publisher=navbharattimes.com|}}</ref> | occupation = ब्यास, गीतकार | years_active = 1988–अभिले | associated_acts = | label = | website = }} '''राजमोहन''' एगो डच [[भोजपुरी]] गायक हवन। ई सरनामी भोजपुरी मे ढेरे गीत लिखले आ गवले बाड़न।<ref>{{cite web|url=https://wap.business-standard.com/article-amp/pti-stories/raj-mohan-the-man-popularising-bhojpuri-songs-in-suriname-118122300506_1.html|title=राज मोहन: भोजपुरी गीत के सूरीनाम में लोकप्रिय करे वाला गायक|publisher=business-standard.com|4=|access-date=2020-09-01|archive-date=2018-12-23|archive-url=https://web.archive.org/web/20181223190159/https://wap.business-standard.com/article-amp/pti-stories/raj-mohan-the-man-popularising-bhojpuri-songs-in-suriname-118122300506_1.html|url-status=dead}}</ref> 2011 में इ आपन पहिला पॉप एल्बम रिलीज़ कइलन जेकरा में सरनामी-भोजपुरी बोल के हिंदी गाना अउरी कविता संगे मिलावाल रहे, सब इंकर लिखल अउरी संगीत दिहल रहे।<ref>{{cite web|url=https://hindi.timesnownews.com/amp/bollywood/bollywood/article/bhojpuri-singer-raj-mohan-wants-to-make-bhojpuri-music-in-every-genre-says-in-exclusive-interview/397006|title=मुश्किलों में संगीत के ना साथ छोड़ले, इ गायक नीदरलैंड में रहके भोजपुरी में पहचान बनवलन|publisher=hindi.timesnownews.com|4=|access-date=2020-09-01|archive-date=2019-04-09|archive-url=https://web.archive.org/web/20190409072303/https://hindi.timesnownews.com/amp/bollywood/bollywood/article/bhojpuri-singer-raj-mohan-wants-to-make-bhojpuri-music-in-every-genre-says-in-exclusive-interview/397006|url-status=dead}}</ref> इ पहिलका बेरी रहे सूरीनाम के इतिहास में की सरनामी-भोजपुरी के कंटेम्पररी के रूप में इस्तेमाल कइल गइल।<ref>{{cite web|url=https://scroll.in/article/743625/the-sounds-of-surinam-when-bhojpuri-creole-meets-carribbean-beats|title=सूरीनाम के संगीत: जब भोजपुरी-क्रियोल मिलल कैरेबियन ताल संगे|publisher=scroll.in|}}</ref> राज मोहन आपन एल्बम 'कंट्राकी (2005)' ले सरनामी-भोजपुरी गीत के इजात कइलन, गीत अउरी ग़ज़ल स्टाइल में, जेकर कुल्ह दुनिया में बड़ाई भइल।<ref>{{cite web|url=http://www.rajmohan.nl/|title=राजमोहन के जीवनी|3=|access-date=2020-09-01|archive-date=2020-09-24|archive-url=https://web.archive.org/web/20200924173814/http://www.rajmohan.nl/|url-status=dead}}</ref> == शुरूआती जिनगी अउरी पेशा == राज सूरीनाम केें पंचवा-पीढ़ी बिहारी-भारतीय बाड़न, इंकर पुरखन के अंग्रेजन सूरीनाम में गिरमिटिया मजदूर बना के ले गइल रहन सन। इ अट्रेक्ट, नीदरलैंड में बड भइलन अउरी छोट उमिर में संगीत में रूचि देखऽवलन।<ref>{{cite web|url=https://zeenews.india.com/hindi/entertainment/bhojpuri/raj-mohan-popular-songs-are-made-in-bhojpuri-songs-in-suriname/482037/amp|title=बिदेस के धरती प भोजपुरी गीतन के प्रसिद्धि दिलावत बाड़े म्यूजिशियन राज मोहन|publisher=Zee News|access date=}}</ref> 12 के उमिर में राज [[सूरीनाम]] ले [[नीदरलैंड]] चल गइले अपना बाबु-माई जोरे। == संदर्भ == {{reflist}} [[श्रेणी:जियत लोग]] [[श्रेणी:भोजपुरी प्लेबैक सिंगर]] {{Singer-stub}} m7syo5nbfwhzdf41wp9mg5fn92pj3dt 803334 803333 2026-07-29T16:21:36Z KiranBOT 38109 removed AMP tracking from URLs ([[:m:User:KiranBOT/AMP|details]]) ([[User talk:Usernamekiran|report error]]) v2.2.9s 803334 wikitext text/x-wiki {{Infobox musical artist | name = राज मोहन | native_name = | image = | caption = राज मोहन 2019 में | image_size = 250 | background = सोलो गायक | birth_name = राज मोहन<ref>{{cite web|url=https://www.thelallantop.com/videos/interview-of-raj-mohan-a-netherlands-based-bhojpuri-singer-musician-and-lyricist/|title=नीदरलैंड्स में रहे वाला सिंगर राज मोहन ले सुनी बिदेस में गावे वाला भोजपुरी गीत|publisher=thelallantop.com|}}</ref> | birth_date = {{Birth date and age|1964|08|28|df=y}} | birth_place = [[पारामारिबो]], [[सूरीनाम]] | death_date = | genre = पॉप , ग़ज़ल<ref>{{cite web|url=https://navbharattimes.indiatimes.com/metro/mumbai/other-news/raj-mohan-is-making-bhojpuri-songs-popular-in-suriname/articleshow/67220654.cms|title=राज मोहन : सूरीनाम में भोजपुरी गाना के प्रसिद्ध करत बाड़न|publisher=navbharattimes.com|}}</ref> | occupation = ब्यास, गीतकार | years_active = 1988–अभिले | associated_acts = | label = | website = }} '''राजमोहन''' एगो डच [[भोजपुरी]] गायक हवन। ई सरनामी भोजपुरी मे ढेरे गीत लिखले आ गवले बाड़न।<ref>{{cite web|url=https://wap.business-standard.com/article-amp/pti-stories/raj-mohan-the-man-popularising-bhojpuri-songs-in-suriname-118122300506_1.html|title=राज मोहन: भोजपुरी गीत के सूरीनाम में लोकप्रिय करे वाला गायक|publisher=business-standard.com|4=|access-date=2020-09-01|archive-date=2018-12-23|archive-url=https://web.archive.org/web/20181223190159/https://wap.business-standard.com/article-amp/pti-stories/raj-mohan-the-man-popularising-bhojpuri-songs-in-suriname-118122300506_1.html|url-status=dead}}</ref> 2011 में इ आपन पहिला पॉप एल्बम रिलीज़ कइलन जेकरा में सरनामी-भोजपुरी बोल के हिंदी गाना अउरी कविता संगे मिलावाल रहे, सब इंकर लिखल अउरी संगीत दिहल रहे।<ref>{{cite web|url=https://hindi.timesnownews.com/amp/bollywood/bollywood/article/bhojpuri-singer-raj-mohan-wants-to-make-bhojpuri-music-in-every-genre-says-in-exclusive-interview/397006|title=मुश्किलों में संगीत के ना साथ छोड़ले, इ गायक नीदरलैंड में रहके भोजपुरी में पहचान बनवलन|publisher=hindi.timesnownews.com|4=|access-date=2020-09-01|archive-date=2019-04-09|archive-url=https://web.archive.org/web/20190409072303/https://hindi.timesnownews.com/amp/bollywood/bollywood/article/bhojpuri-singer-raj-mohan-wants-to-make-bhojpuri-music-in-every-genre-says-in-exclusive-interview/397006|url-status=dead}}</ref> इ पहिलका बेरी रहे सूरीनाम के इतिहास में की सरनामी-भोजपुरी के कंटेम्पररी के रूप में इस्तेमाल कइल गइल।<ref>{{cite web|url=https://scroll.in/article/743625/the-sounds-of-surinam-when-bhojpuri-creole-meets-carribbean-beats|title=सूरीनाम के संगीत: जब भोजपुरी-क्रियोल मिलल कैरेबियन ताल संगे|publisher=scroll.in|}}</ref> राज मोहन आपन एल्बम 'कंट्राकी (2005)' ले सरनामी-भोजपुरी गीत के इजात कइलन, गीत अउरी ग़ज़ल स्टाइल में, जेकर कुल्ह दुनिया में बड़ाई भइल।<ref>{{cite web|url=http://www.rajmohan.nl/|title=राजमोहन के जीवनी|3=|access-date=2020-09-01|archive-date=2020-09-24|archive-url=https://web.archive.org/web/20200924173814/http://www.rajmohan.nl/|url-status=dead}}</ref> == शुरूआती जिनगी अउरी पेशा == राज सूरीनाम केें पंचवा-पीढ़ी बिहारी-भारतीय बाड़न, इंकर पुरखन के अंग्रेजन सूरीनाम में गिरमिटिया मजदूर बना के ले गइल रहन सन। इ अट्रेक्ट, नीदरलैंड में बड भइलन अउरी छोट उमिर में संगीत में रूचि देखऽवलन।<ref>{{cite web|url=https://zeenews.india.com/hindi/entertainment/bhojpuri/raj-mohan-popular-songs-are-made-in-bhojpuri-songs-in-suriname/482037/amp|title=बिदेस के धरती प भोजपुरी गीतन के प्रसिद्धि दिलावत बाड़े म्यूजिशियन राज मोहन|publisher=Zee News|access date=}}</ref> 12 के उमिर में राज [[सूरीनाम]] ले [[नीदरलैंड]] चल गइले अपना बाबु-माई जोरे। == संदर्भ == {{reflist}} [[श्रेणी:जियत लोग]] [[श्रेणी:भोजपुरी प्लेबैक सिंगर]] {{Singer-stub}} h1mulj3arzccchlv8sen42zz9ymdt08 आरा हिले छपरा हीले बलिया हीलेला 0 76200 803317 729411 2026-07-29T16:20:28Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803317 wikitext text/x-wiki '''आरा हीले छपरा हीले बलिया हिलेला''' एगो [[भोजपुरी लोकगीत]] हऽ जेकरा ढेरे फिलिम आ एलबम मे फेर से बनावल गइल बा।<ref>{{Cite book|last=घोष|first=अविजित|title=सिनेमा भोजपुरी|publisher=पेंगुइन यू के|year=|isbn=|location=|pages=}}</ref> == रूपांतरन == === 1977 फिलिमी रूपांतरन === भोजपुरी फिलिम ''दंगल'' मे हेकरा फेर से बनावल गइल रहे आ हेकर लेखक रजपती रहन।<ref>{{Cite book|last=घोष|first=अविजीत|title=सिनेमा भोजपुरी|publisher=|year=|isbn=|location=|pages=}}</ref> === 1996 फिलिमी रूपांतरन === [[मिथुन चक्रवर्ती]] के हिंदी फिलिम ''अंगारा'' मे ई गाना के पुनर्निर्माण कइल गइल रहे। === 1996 फिलिमी रूपांतरन === बॉलीवुड फिलिम "अपने दम पर" मे एह गाना के पुनर्निर्माण भइल जेकरा [[गोविंदा]] गइलन। === 2012 फिलिमी रूपांतरन === भोजपुरी चलचित्र ''खून पसीना'' मे हेकर पुनर्निर्माण भइल। === 2019 फिलिमी रूपांतरन === सिद्धार्थ मल्होत्रा आ [[परिणीति चोपड़ा]] के चलचित्र ''जबरिया जोड़ी'' मे ई गाना के पुनर्निर्माण भइल।<ref>{{Cite news|last=|first=|date=|title=Jabariya Jodi: Parineeti Chopra, Sidharth Malhotra to launch song, UP Hile Zilla Hile, a remake of Bhojpuri song Ara Hile|work=हिंदुस्तान टाइमस|url=https://www.hindustantimes.com/bollywood/jabariya-jodi-parineeti-chopra-sidharth-malhotra-to-launch-song-up-hile-zilla-hile-a-remake-of-bhojpuri-song-ara-hile/story-moox4VJjXQFw5paUgg6GSK_amp.html|url-status=live|access-date=}}</ref> === 2020 रीमिक्स === भोजपुरी गायक [[रितेश पांडे]] एगो स्वतंत्र एल्बम मे एह गाना के फेर से बनवलन।<ref>{{Cite web|title=Bhojpuri Song: रितेश पांडे के नये गाने 'काशी हिले, छपरा हिले, पटना हिलेला'|url=https://www.prabhatkhabar.com/amp/story/entertainment%252Fritesh-pandey-new-bhojpuri-song-kashi-hile-chhapra-hile-goes-viral-watch-video|last=|first=|date=|website=|url-status=live|archive-url=|archive-date=|access-date=}}</ref> अउरीयो सभ भोजपुरी फिलिम मे ई गाना बनल आ बहुत प्रख्यत भइल। == सनरभ == [[श्रेणी:भोजपुरी लोकगीत]] {{Poem-stub}} pjei5bb9fjlzkd704ywo65de9i1zwkc सृष्टिकर्ता 0 76227 803328 788948 2026-07-29T16:21:14Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803328 wikitext text/x-wiki '''सृष्टिकर्ता''' धर्म सभ मे एगो अइसन [[परमेसर]] के कहल जाला जे [[पृथिवी]] चाहे [[ब्रह्मांड]] के निर्माता होला। [[एक ईश्वर वाद]] मे ऊ अकेल परमेसर के सृष्टिकर्ता मानल जाला। ==एक ईश्वर वाद== ===अतेनत्व=== {{See also|अतेन धरम}} अतेनत्व, नवका राज बजी [[अखेनातेन]] आ रानी [[नफ़्तिर्ती]] के १३३० ईशा पूर्व मे चालू कइल गइल। नीज आ उपसक लोग ला ऊ लोग नावा राजधानी बनवलस। ओकर पिता उनकर एक से बेसी ईश्वर वाला धरम मे आउर सभ भगवान संगे [[अतेन]] के पूजत रहन। फैरो के अंत के संगे संगे अतेनत्व ओ ओरा गइल। कुछु बिद्वानन के ई कहनाम बा की अतेनत्व, सभ से पुरान एक ईश्वर वादी धरम मे ले एगो बाटे। ===इब्राहिमी धरम=== इब्राहिमी उत्पत्ति के ब्योरा दू गो खिसा से बनल बाटे, जवन की [[आदि पुस्तक]] के पहिलका अध्याय से लमसम मिलेला। पहीलका खिसा, दैवीय उपचार आ पूर्ति के एगो दोहरावदार संरचना आ ई काहनाम "x वा दिन, सांझ आ भिनुसार रहे" उत्पत्ती के छव दिन मे हर दिन ला नियोजित करेला। पहिकला तीन दिन मे बखरा लागेला: पहिला दिन अन्हार आ अंजोर के बखरा लगावेला, दूसरका दिन ऊपर आ नीचे के पानी के, तीसरका दिन भुईं आ सागर के। अगिला तीन दिन सभ के बस्तू दिहल गइल, चउथका दिन अन्हार आ अंजोर के [[सुरुज]], [[चंद्रमा|चान]] आ [[तारा|जोन्ही]] दिहल गइल, पंचमा दिन सागर आ आकाश के मछ आ पाखी दिहल गइल। छवमा दिन भुईंया प मानुस सभ के उतारल गइल। ===सिख धरम=== सिख धर्म ओ सृष्टिकर्ता मे अस्था राखेला जेकरा ई [[वाहेगरू]] बोलेला। वाहेगरू नीरकर आउर अंतरधयान हवन। [[श्रेणी:धर्म]] {{Reli-stub}} tr7fjh6uonaj3dlo6coxa8h4iq3vy6z मोइन उल हक स्टेडियम 0 76249 803327 722308 2026-07-29T16:21:10Z SM7Bot 7808 /* मैदान के जानकारी */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803327 wikitext text/x-wiki {{Infobox cricket ground | ground_name = मोइन उल हक स्टेडियम | nickname = | image = Moin-Ul-Haque Stadium.JPG | Type. = International Cricket Stadium | caption = | country = भारत | location = [[पटना]], [[बिहार]], [[भारत]] | coordinates = {{coord|25|36|28|N|85|10|4|E|type:landmark|display=it}} | establishment = 1969 | seating_capacity = 25,000 | owner = [[बिहार क्रिकेट संघ]] | operator = [[बिहार क्रिकेट संघ]] |name after = | tenants = भारतीय क्रिकेट टीम<br/>बिहार क्रिकेट टीम<br/> | end1 = गंगा एंड | end2 = पविलिअन एंड | international = true | firstodidate = 15 November, | firstodiyear = 1993 | firstodihome = Sri Lanka | firstodiaway = Zimbabwe | lastodidate = 27 February, | lastodiyear = 1996 | lastodihome = Zimbabwe | lastodiaway = Kenya | onlywtestdate = 17–19 November | onlywtestyear = 1976 | onlywtesthome = India | onlywtestaway = West Indies | firstwodidate = 5 January | firstwodiyear = 1978 | firstwodihome = India | firstwodiaway = New Zealand | lastwodidate = 22 December | lastwodiyear = 1997 | lastwodihome = India | lastwodiaway = South Africa | date = 8 November | year = 2019 | source = http://content-www.cricinfo.com/india/content/ground/58373.html Moin-Ul-Haque Stadium, Cricinfo }} '''मोइन उल हक स्टेडियम''' [[बिहार]] राज्य के [[पटना]] मे एगो स्टेडियम बाटे। एहमे तीन गो [[एकदिवसीय अंतराष्ट्रीय]] मैच भइल बा जेमे से एगो मैच 1996 के विश्व कप के रहे। एकर क्षमता 24000 दरसक के बाटे। ई बिहार रणजी दल के घरेलू मैदान हटे। बिहार सरकार अबहिं एकर बिकास कऽ के 42,500 के क्षमता पे काज करऽता। बिहार के क्रिकेट टीम एह मैदान प बहुते रंजी मैच खेलल बा। ==इतिहास== पहिले एकर नांव राजेन्द्र नगर स्टेडियम रहे। 1970 मे मोइन उल हक के नांव प हेकर नांव धराइल। जेवन की भारतीय ओलींपिक संघ के मुख सचिव रहन आ बिहार क्रिकेट संघ के संस्थापक लोग मे से एगो रहन।<ref>{{cite web |url=http://content-usa.cricinfo.com/india/content/story/256671.html |title=Bihar Cricket Association reinstated |accessdate=2008-09-10 |url-status=live |archiveurl=https://web.archive.org/web/20080911171106/http://content-usa.cricinfo.com/india/content/story/256671.html |archivedate=11 September 2008 |df=dmy-all }}</ref> ==मैदान के जानकारी== ई एगो आधुनिक आ अंतराष्ट्रीय स्तर के स्टेडियम रहे।<ref>{{cite web |url=http://beta.cricket.yahoo.com/cricket/profile/ground-profile?gid=98 |title=Moin-ul-Haq Stadium (Moinul Haque Stadium) |accessdate=2008-09-10 |url-status=dead |archiveurl=https://archive.is/20120707010310/http://beta.cricket.yahoo.com/cricket/profile/ground-profile?gid=98 |archivedate=7 July 2012 |df=dmy-all }}</ref> बाकिर भारतीय क्रिकेट नियंत्रण बोर्ड आ बिहार क्रिकेट संघ के लड़ाई के चलते एह स्टेडियम मे मैच के आजोजन ना होखला के चलते ई पुरान हो गइल आ हेकर हालत खराब भ गइल। [[झारखंड]] आ [[बिहार]] मे बखरा लगला के बादि बिहार क्रिकेट के मान्यता चल गइल आ एहिजा रणजी ओ होखल बन हो गइल। बाकिर फेर मान्यता मिलला के बादि एहिजा रणजी होला आ बिहार रणजी दल एहीजे मेजबान बनि के खेलेला। एह मैदान के अंतराष्ट्रीय मानक प तेयार कइल जाता। == संदर्भ == {{Reflist|33em}} [[श्रेणी:पटना]] [[श्रेणी:बिहार में खेलकूद]] nnsfdi8tn2lo7q01s0o158pr8zhasl3 क्रिकेट बैट 0 76259 803318 693259 2026-07-29T16:20:32Z SM7Bot 7808 /* बनाई */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803318 wikitext text/x-wiki [[File:A modern Cricket bat (back view).jpg|thumb|बैट कर एगो छबि]] '''क्रिकेट बैट''' [[क्रिकेट]] मे बैट्समैन के प्रयोग मे आवे अला एगो समान बाटे। ई दू भाग मे होला, एक भाग गोल काठ के होला जेने से खेलत बजी धाइल जाला आ दोसरा भाग सपाट आ चउडा होला जेकर मदद से गेना मारल जाला। एकर मानक लंबाई ३८ इंच आ चउड़ई सावा चार ईंच होखेला। १९७९ मे बनल नियम के चलते बैट खाली काठे के बन सकेला अउरी कुछु के ना। ==बनाई== [[File:Pod shaving of cricket bat by hands.jpg|thumb|क्रिकेट बैट के छिलाई]] बैट के पटरा काठ के होला, जवन की जोन पटी से गेना के मारल जाला ओह पटी समतल रहेला आ दोसर ओरे तिरकोन लेखा उठल रहेला आ बीच मे बसी मोटहन होला। पारंपरिक रूप से बैट वीलो के काठ से बनेला, मुख रूप से वाइट वीलो ([[भोजपुरी]]: उज्जर वीलो) चाहे क्रिकेट बैट वीलो से बनावल जाला,जे विलो के एगो परकार होला। एहमे लाइनसीड के तेल पियावल जाला जेकरा से बैट हाली बाउर ना होखे। वाइट वीलो के परजोग एह से कइल जाला काहेकी ई बहुते हलुक आ बरियार होला। बैट के पटरा (ब्लेड) एगो लाम हैंडल से जोड़ल रहेला। हैंडल प एगो रबर के ग्रिप लागल रहेला। [[श्रेणी:क्रिकेट]] {{Sport-stub}} 1bud43zs9wsn2kspxgf35kjuhokt13u चंपारण मीट 0 76302 803305 799100 2026-07-29T16:07:12Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803305 wikitext text/x-wiki {{Infobox food | name = चंपारण मीट | image = Champaran meat.jpg | image_size = | image_alt = | caption = | alternate_name = अहुना, हांडी मीट, बटलोही | type = | course = मुख्य खाना | country = [[भारत]] | region = [[चंपारण]], [[बिहार]] | national_cuisine = | creator = <!-- or | creators = --> | year = | mintime = | maxtime = | served = Hot | main_ingredient = चिकन, मटन आ भारतीय मसाला | minor_ingredient = [[घीव]], [[सरसों क तेल]], [[दही]], [[प्याज]], [[अदरक]], [[सूखा फल]] | variations = कईयन ठे | serving_size = 100 ग्राम | calories = | calories_ref = | protein = | fat = | carbohydrate = | glycemic_index = | similar_dish = मटन करी, चिकन करी | cookbook = | other = }} '''चंपारण मीट''', जेकरा के '''अहुना''', '''हांडी मीट''' चाहे '''बटलोही''' कहल जाला, [[बिहार]] के जिला [[चंपारण]] से निकले वाला पकवान हवे। मीट, मने की मांस, के सरसों के तेल अउरी घीव, लहसुन, पियाज, अदरक के मिलजुल पीसल मसाला के पेस्ट के संगे मैरीनेट कइल जाला। माटी क हांड़ी में पाके खाती चढ़ा दिहल जाला आ ओकरा बाद ओकर मुँह गूंथल आटा से नीक से सील कइल जाला। एकरा के लकड़ी के आग के मद्धिम आँच प लहे-लहे पकावल जाला आ खाना बनावे के दौरान बीच-बीच में हिलकोरल जाला। सवाद आ खाना बनावे के समय एह बात प निर्भर करे ला की मीट कईसन बाटे। ==संदर्भ== {{Reflist}} [[श्रेणी:बिहार के खाना]] [[श्रेणी:चंपारण जिला]] {{Food-stub}} {{bihar-stub}} 3dftxxd2yagekjod65b4260tf83bygq सुशांत सिंह राजपूत 0 76426 803326 729787 2026-07-29T16:21:06Z SM7Bot 7808 /* top */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803326 wikitext text/x-wiki {{Infobox person | name = सुशांत सिंह राजपूत | image = Sushant Singh Rajput snapped at the promotions of 'M.S. Dhoni - The Untold Story' (cropped).jpg | image_size = 270px | caption = "एमएस धोनी: द अनटोल्ड" स्टोरी के परचार करत | birth_date = {{birth date|1986|01|21|df=y}}<ref>{{cite web |title='Happy Birthday, Sushant Singh Rajput. Keep That Childlike Smile Always Alive,' Tweets Kriti Sanon |url=https://www.ndtv.com/entertainment/happy-birthday-sushant-singh-rajput-keep-that-childlike-smile-always-alive-tweets-kriti-sanon-1802788 |website=[[NDTV]] |author=Shruti Shiksha |date=21 January 2018 |accessdate=6 December 2018}}</ref> | birth_place = [[पटना]], [[बिहार]], भारत<ref name="time_Madh"/> | death_date = {{Death date and age|df=y|2020|06|14|1986|01|21}}<ref>{{Cite web |date=14 June 2020 |title=Sushant Singh Rajput commits suicide at Mumbai home |url=https://www.indiatoday.in/movies/celebrities/story/sushant-singh-rajput-commits-suicide-at-mumbai-home-1688886-2020-06-14 |access-date=14 June 2020 |website=[[India Today]]}}</ref> | death_cause = फसरी लगाई के आत्महत्या<ref>{{Cite web |date=14 June 2020 |title=Cite web |date=14 June 2020 |title=Sushant Singh Rajput commits suicide at Mumbai home |url=https://www.indiatoday.in/movies/celebrities/story/sushant-singh-rajput-commits-suicide-at-mumbai-home-1688886-2020-06-14 |access-date=14 June 2020 |website=[[India Today]]}}</ref> | death_place = [[मुंबई]], [[महाराष्ट्र]], भारत | nationality = भारतीय | occupation = {{hlist|अभिनेता|नर्तक}} | years_active = 2008–20 | height = | alma_mater = }} '''सुशांत सिंह राजपूत''' एगो भारतीय अभिनेता रहन। ई टीवी सीरियल सभ से आपन कैरियर चालू कइलन। हिनकर पहिला शो "किस देश में है मेरा दिल रहे"। ई "पवित्र रिश्ता" से बहुते प्रख्यात भइलन। हिनकर पहिलका फिलिम "काय पो छे" (2013) रहे। सुशांत आपन फिल्म के शुरुआत 2013 मे काय पोछे से कइलन। एकरा बाद उह देसी रोमांस, पीके, डिटेक्टीव ब्योमकेश बक्शी में काम कइलन। 2016 के फिल्म जेकर नाव एम एस धोनी: द अनटोल्ड स्टोरी में ऊ महेंद्र सिंह धोनी के भूमिका कइले बाड़न। जेकरा खातिर उनकरा के खूब पसंद कइल गइल। अभिनय के आलावा ऊ अलग अलग कार्यक्रम जयसे की '''सुशांत4एजुकेशन''' में शामिल रहन। ऐकर आलावा उह कुछ प्रौद्योगिकी कंपनी के भी संस्थापक कइले रहन। 14 जून 2020 के सुशांत सिंह राजपूत के देह पंखा से लटकल मिलल, जेकर जांज केंद्रीय अन्वेषण ब्यूरो द्वारा कइल जाता। == जिनगी == हिनकर जनम पटना मे भइल बाकिर हीनकर गाँव [[पूर्णिया]] बाटे। हिनकर एगो बहिनी, मितु सिंह, राज्य स्तर के किरकेटर बाड़ी। हिनकर माई के देहांत 2002 मे भ गइल रहे, जेकरा से हिनका बहुत बड़ सदमा लागल रहे आ ओही बरिस हीनकर परिवार दिल्ली चलि गइल। == संदर्भ == {{Reflist|33em}} [[श्रेणी:1986 में जनम]] [[श्रेणी:2020 में निधन]] [[श्रेणी:हिंदी फिलिम एक्टर]] lsxubrg30obvthn171ndlxt83nwxd6t वुमन पावर लाइन 1090 0 76715 803320 781508 2026-07-29T16:20:41Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803320 wikitext text/x-wiki '''''वूमेन पावर लाइन''''' ({{Lang|en|Women Power Line}}) [[उत्तर प्रदेश पुलिस]] क भारतीय समाज के परेशान करे वाली ई असामयिक समस्या: यौन उत्पीड़न, और एकर जड़ से हमरे समाज क पितृसत्तात्मक मानसिकता क गलतफहमी क विरुद्ध चलावे जाये वाला अभियान बा।<ref>{{Cite web|url=http://uphome.gov.in/wpl-hi.htm|title=वूमेन पावर लाइन|website=गृह विभाग, उत्तर प्रदेश सरकार|archive-url=https://web.archive.org/web/20191221030407/http://uphome.gov.in/wpl-hi.htm|archive-date=21 दिसंबर 2019|access-date=26 जून 2020}}</ref> ई महीलन क सुरक्षा खातिर काम करे वाला सरकारी कॉल सेंटर बा। ई 24x7 काम करे ला। ==इतिहास== उत्तर प्रदेश में महीलन क सुरक्षा अऊर उनकरे में जागरूकता लावे खातिर औउर महिला उत्पीड़न क घटना के सख्ती से रोके खातिर उत्तर प्रदेश क अखिलेश यादव सरकार 2012 में वुमन पावर लाइन 1090 क स्थापना कइले । वूमेन पावर लाइन 1090 सेवा के अऊर मजबूत बनावे खातिर 2014 में "वुमेन सिक्योरिटी ऍप 1090" (महिला सुरक्षा ऍप १०९०) सेवा भी प्रारंभ कइल गइल बा । ==संदर्भ== {{reflist}} [[श्रेणी:उत्तर प्रदेश में शिक्षा]] [[श्रेणी:नारीवाद]] {{UP-stub}} s1dkfcs7cc06httpz4uzn9v06j8mh65 भारतीय संविधान के आमुख 0 76756 803360 775393 2026-07-29T16:23:33Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803360 wikitext text/x-wiki [[File:Preamble to the India Constitution Bhojpuri.png|thumb|250px|भारतीय संविधान के आमुख]] '''भारतीय संविधान के आमुख''' एगो नन्हीचुक्की चिन्हात्मक कहनाम बाटे। ई देशवासीयन के राह देखावेला, आ संविधान के सिद्धांतन के प्रस्तुत करेला। एकरा के [[संविधान सभा]] द्वारा 26 नवम्बर 1949 के स्विकारल गइल आ 26 जनवरी 1950 के भारत के [[गणतंत्र दिवस (भारत)|गणतंत्र दिवस]] के रूप में मनावल गइल। ।<ref>{{cite book|last1=Baruah|first1=Aparijita|title=Preamble of the Constitution of India: An Insight and Comparison with Other Constitutions|date=2006 |publisher=Deep & Deep|location=New Delhi|isbn=978-81-7629-996-1|page=177|url=https://books.google.com/?id=aPujAJ1UVqUC&pg=PA177#v=onepage&q&f=true|accessdate=12 November 2015}}</ref> एहमे प्रजा के आस अउर अकांक्षा के वर्णन भेटाला। <ref>[http://legislative.gov.in/constitution-forty-second-amendment-act-1976 42th Amendment]</ref> आमुख के भारतीय संविधान के आत्मा कहल जाला। ==लेखा== [[देवनागरी]] में; {{cquote|हमनि, भारतीय प्रजा, भारत के एगो सुसंपन्न <br/>परमाधिपत्यक, सामाजवादी, धम्मनिरपेक्ख, लोकतनंत्रातमक,<br/>गणराज्य के रूप में स्थापित करे खातिर <br/>तथा एकर सगरी नागरिकन के;<br/>सामाजिक, आर्थिक, आ राजनीतिक न्याय;<br/> बिचार, अभिव्यक्ति, बिश्वास, धर्म<br/>आ आराधना के स्वतंत्रता;<br/>आदर आ तक के समानता प्राप्त करावे खातीर<br/>तथा ई सौसे प्रजा मे, व्यक्ति के गौरव आ राष्ट्र के एकता <br/>आ अखण्डिता, निर्धारित करावे ऐसन <br/>बन्धुत्ता के बिक्सावेला, <br/>गंभीरतापूर्ण संकल्प कके आपन संविधान सभा मे आज 26 नवंबर 1949 (अगहन सुद शुक्ल सप्तमी विक्रम संवत दु हजार छव) के रोज <br/> एहसे ई संविधान के स्वीकार कके, एह के अधिनियमित आ आत्मसमर्पित करऽतानीजा।}} {{clear}} ==ऐतिहसिक पूर्वचरितम्== [[चित्र:Emblem_of_India_(without_motto).svg|भारत के प्रतीक चिन्हा|alt=Emblem of India|thumb]] आमुख उद्देश्य प्रस्ताव पर आधारित बा जेके 13 दिसंबर 1946 के संविधान सभा में [[जवाहरलाल नेहरू]] द्वारा प्रस्तुत कइल गइल जे 22 जनवरी 1947 के सकारऽल गइल आ संविधान सभा द्वारा 26 नवंबर 1949 के अपनावल गइल आ 26 जनवरी 1950 से लागू भइल। [[भीमराव आंबेडकर|बी.आर. आम्बेडकर]] आमुख के लेके कहले के; <blockquote> ''साच्चो में, ई जिनगी के एगो ऐसन डगर रहे, जऽवन स्वतंत्रता, समानता आ बन्धुत्ता के जिनगी के सिद्धांत के रूप में चिन्हऽत रहे आ जेकरा के एक दोसरा से अलगऽ ना कइल जा सकेला: स्वतंत्रता के समानता से अलग ना कइल जा सकेला; समानता के स्वतंत्रता से अलग ना कइल जा सके। ना तऽ स्वतंत्रता आ समानता के बन्धुत्ता से अलग कइल जा सके। बिना समानता के स्वतंत्रता से बहुत लोगीन पर तिनका लोगीन के वर्चस्व पैदा होला। बिना स्वतंत्रता के समानता व्यक्तिगत पहल के मार देवेला। बिना बन्धुत्ता के स्वतंत्रता आ समानता स्वाभाविक तरीका से ना बन सकेला।'' </blockquote> जब [[संविधान सभा]] एह आमुख पर बहस करत रहे तब ई राय दिहल गइल के भारत के नाँव बदल के "भारतीय समाजवादी गणराज्य संघ" रखल जाय, मानो के भारत [[सोवियत यूनियन|सोवियत संघ]] के नकल करत होखे, यद्दपि अन्य सदस्य एह बात से असहमत रहले। ईहो बात पर बहस भइल के आमुख में भगवान आ गाँधी नाम शामिल कइल जाव के ना। पहिलका के मतदान तब हो गइल जब 68 गो सदस्य ‘भगवान’ के खिलाफ वोट दिहले। एच.वी.कामथे बेताब होके अभिप्राय दिल्ली के, ‘महासये, ई हमनी के इतिहास के एगो करिया दिन बा। भगवान भारत के बचावस।' जबके बाद मे - गाँधी के नाम शामिल करे के सुझाव [[ब्रजेश्वर प्रसाद]] नाकार दिहले, जिन्करा लागऽत रहे के 'सड़ल संविधान' - जवन अमेरिकी सुप्रीम कोर्ट के मामला आ भारत सरकार के कानून पर आधारित बा, एह मे 'गाँधीवादी' प्रकृति नइखे, से गाँधी के नाँद शामिल ना होखे। प्रसाद कहले के, "हम नईखी चाहत कि [[महात्मा गांधी]] के नाँव एह संविधान में शामिल होखे, काहेंसेके इ गांधीवादी संविधान नइखे। जदी हमनी के लगे गांधीवादी संविधान रहित तऽ हम पहिलका आदमी रहती जवन आपन समर्थन देती। गाँधी के नाम फेरा लेवे के चाहीं।" [[भारत के सुप्रीम कोर्ट|भारतीय सर्वोच्च न्यायालय]] मूर रूप से बेरुबारी मामला में [[राष्ट्रपति]] संदर्भ में फैसला कइले रहे के आमुख भारतीय संविधान के अविभाज्य अङ नइखे, आ एही चल्ते ई अदालतन में लागू ना हो सकेला। यद्दपि उहे अदालत 1973 के केशवानन्द मामला में पहिले के फैसला के नामंजूर क देलस आ मान लेलस के आमुख के उपयोग संविधान के अस्पष्ट क्षेत्रन के व्याख्या करे खातीर कइल जा सकेला, जहवा अलग-अलग व्याख्या खुदे के प्रस्तुत करेले। 1995 में केंद्र सरकार बिरुद्ध भारतीय एल.आई.सी के मामला में सुप्रीम कोर्ट एक बेरी फेरु से कहऽलस कि आमुख संविधान के अविभाज्य अङ बा। आमुख जे के मूर रूप से निर्माणल गइल रहे,ओह मे राज्य के परमाधिपत्यक, लोकतांत्रिक, गणतंत्र के रूप में बतावल गइल रहे, बाकीर बाद में 42वा संशोधन में [[इमरजेंसी (भारत)|आपातकाल]] के दौरान [[संसद]] द्वारा धम्मनिरपेख आ समाजवादी शब्द पारित कइल गइल। आमुखवाला पन्ना के सङे अस्सल [[भारतीय संविधान]] के बाकियो पन्ना के डिजाइन आ सजावट [[जबलपुर]] के मशहूर चित्रकार [[बेवहर राममनोहर सिन्हा]] कइले रहलें जे तब [[आचार्य नन्दलाल बोस]] के सङे [[शांतिनिकेतन]] में रहत रहलें। नन्दलाल बोस बगेर कवनो बदलाव कइले बेवहर राममनोहर सिन्हा के कलाकृति के समर्थन कइले। एह पन्ना के निचला दाहिनवारी कोना में [[देवनागरी लिपि]] में बेवहर राममनोहर सिन्हा के नन्हिचुक्की हस्ताक्षर "राम" बा। सुलेख [[प्रेम बिहारी नारायण रायजादा]] कइले रहले। आमुख में 18 दिसंबर 1976 के मात्र एक्के बेरीया संशोधन भइल। भारत में आपातकाल के बेरा [[इंदिरा गाँधी]] सरकार संविधान के 42वा संशोधन में केतनहे परिवर्तन कइलस। एह संशोधन से "परमाधिपत्यक" आ "लोकतांत्रिक" शब्दन के बीच "समाजवादी" आ "धम्मनिरपेख" शब्द जोड़ल गइल आ "राष्ट्र के एकता" शब्द के परिवर्तित कके "राष्ट्र के एकता आ अखण्डित्ता" धऽ दिहल गइल। साल 1994 में "एस.आर.बोमाई बिरुद्ध भारत संघ" मामला बेरा भारतीय सर्वोच्च न्यायालय के कहऽनामा रहे के [[धम्मनिरपेखता]] मुर संरचना सिद्धांत के हिस्सा बाटे। ==वचनऽबोधऽ== ===परमाधिपत्यक === परमाधिपत्यक के मतलब होला कवनो देश के स्वतंत्र अधिकार। माने ओकरा लगे कवनो विषय पर कानून बनावे के अधिकार बा; आ ई कवनो दोसरा देश / बाहरी शक्ति के नियंत्रण में नइखे। परम माने सर्वोच्च आ आधिपत्यक माने अधिकार रखऽनिहार से परमाधिपत्यक के शाब्दिक अर्थ जेकर अधिकार सर्वोच्च बा आमुख अनुसार भारत के अधिकार। आमुख अनुसार भारतीय संविधान भारत के एगो [[संप्रभुता|परमाधिपत्यक]], लोकतांत्रिक, [[रिपब्लिक|गणतंत्र]] के रूप में स्थापित करेला आ आमुख में उल्लेखित सुपरिभाषित वचनन के सुरक्षित करेला भारत के जनता गंभीर संकल्प के अनुसरण करता। परमाधिपत्यक से तात्पर्य सर्वोच्च आ अंतिम शक्ति से बा। ई वास्तविक, साधारण, कानूनी, राजनीतिक, व्यक्तिगत भा बहुवचनीय हो सकेला। राजतंत्र में परमाधिपत्यकता राजा लोग के व्यक्तियन के दिहल जात रहे। बाकिर वर्तमान दुनिया में अधिकतर गणतंत्रवादी सरकारन में परमाधिपत्यकता जनऽता के चुनल प्रतिनिधियन के हस्तांतरित होला। ===समाजवादी=== 1976 मे 42वा संशोधन द्वारा ई शब्द के जोड़े से पहिले संविधान में राज्य नीति के निर्देशात्मक सिद्धांत के रूप में समाजवादी सामग्री रहे। इहावा उपयोग कइल गइल समाजवादी शब्द लोकतांत्रिक [[समाजवाद]] के संदर्भ देला, मने के लोकतांत्रिक, विकासवादी आ अहिंसक माध्यम से समाजवादी लक्ष्य के प्राप्ति। अनिवार्य रूप से एकर मतलब ई बा के धन के बितरण न्याय के माध्यम से समाज द्वारा समान रूप से कइल जाय (काहेसेके धन सामाजिक रूप से पैदा होला), तनीके लोगीन के हान में सहेजल ना रह जाय आ सरकार के सामाजिक-आर्थिक असमानता के काम करे खातिर जमीन आ उद्योग के स्वामीहक के नियंत्रित करे के चाहीं। ===धम्मनिरपेख=== धम्मनिरपेख माने के सरकार आ धम्मसमूहं के बीच के संबंध संविधान आ कायदा के अनुसार निर्धारित होखेला। ई राज्यशक्ति आ धम्मशक्ति के अलगा कर देला। 18 दिसंबर 1976 के 42वा संशोधनले "धम्मनिरपेख" शब्दो के आमुख में सहेज लेवल गइल। कवनो धम्म मे अंतर नइखे माने [[हिन्दू]], [[बौद्ध]], [[जैन]], [[सिख धर्म|सिख]], [[ईसाई धर्म|ईसाई]] आ [[इस्लाम]] सामान रुप से आदरभावीत बा आ एह से बेसी ई कवनो राज्य के राजधर्म नइखे। भारतीय नागरिकन के आपन धम्म के उपदेश, पालन, आराधना आ प्रसार करेके अनुमति बा। भारत द्वारा साकारल धम्मनिरपेख के अर्थ बतावते अलेक्जेंडर ओविक्स लिखऽले बाड़न के "[[धम्मनिरपेखता|धम्मनिरपेख]], भारतीय संविधान के मूरभुत संरचना के हिस्सा बाटे आ एकर मतलब होला सभे [[धर्म|धम्मन]] के सामान आजादी आ आदर।" ===लोकतंत्र=== [[भारत के लोग|भारत के जनता]] अपना सरकार के "सर्वत्र वयस्क मताधिकार व्यवस्था" से चुनेले, जवना के "एगो बेकत एगो मत" के नाँव से चिन्हऽल जाला। सरकार के ई प्रतिनिधि रूप देश के शासन खातिर उपयुक्त बा काहेसेके एकर जनसंख्या बहुत बड़ आ विविधतापरिपूर्ण बा। भारत के प्रत्येक नागरिक 18 साल आ ओकरा से बेसी उमिर के आ कायदा से प्रतिबन्धित ना होखे ऐसन मनुष मतदान के अधिकारी बाडे़। मोहन लाल त्रिपाठी बिरुद्ध जिला मजिस्ट्रेट मामला में "[[लोकतंत्र]]" शब्द पऽ विचारणा भइल आ कहल गइल के; {| class= "wikitable" |+ !"लोकतंत्र राजनीतिक तत्व के एगो बिचार बाटे, ई एगो आदर्श बा जेकर अभ्यास सांस्कृतिक रुप से समृद्ध आ राजनीतिक रूप से परिपक्व राष्ट्रन द्वारा लोगीन के प्रतिनिधीयन द्वारा शासन के साहारा ले के प्रत्यक्ष आ परोक्ष रूप से करऽल जाला।" |} लोकतंत्र शब्द खाली राजनीतिक लोकतंत्र के ना बलुक सामाजिक आ आर्थिक लोकतंत्रो के कहल जाला। लोकतंत्र के शामिल करे के मुख्य कारण ई बाटे के, लोगीन के आपन प्रतिनिधि चुने के आजादी मिल सके आ अत्याचारियन से देश के बचावल जा सके। ===गणतंत्र=== सरकार के गणतंत्रीय स्वरुप में राज्यप्रमुख के चुनऽल जाला ना के वंशानुगत राजकुमार के राज्यप्रमुख बनावऽल जाला। एह तरीका से ई शब्द ऐसन सरकार के बोध करावे ला जहावा केहू के लगे सार्वजनिकशक्ति , स्वामीत्वाधिकार के रूप मे ना होखे। लोकतंत्र के बिपरीत राजतंत्र, जवऽना में राज्य के प्रमुख के नियुक्ति आजीवन आ राजत्याग तक ले वंशानुगत आधार पर कइल जाला, लोकतांत्रिक गणतंत्र ऐसन एकम हवे जवऽना में राज्य के प्रमुख के सहऽजे आ परोक्ष रूप से एगो निर्धारित कार्यकाल खातिर चुनल जाला। एही तरह से भारत में [[राष्ट्रपति]] होला जे अप्रत्यक्ष रूप से चुनल जाला आ ओकर कार्यकाल निर्धारित होला। ईहवा विशेषाधिकार प्राप्त वर्ग के अभाव बा आ सगरी सार्वजनिक कार्यालय प्रत्येक नागरिक खातिर बगेर कवनो भेदभाव के खुलल बा। ===न्याय=== न्याय, कानून के राज, अत्याचार के किल्लत आ समाज में सभका खातिर समान अधिकार, स्वतंत्रता आ तक पऽ खाडा़ बा । भारत अपना सबितन मे समानता सुनिश्चित करे खातिर सामाजिक, आर्थिक आ राजनीतिक न्याय के खोज-बिन करेला। ( i ) सामाजिक न्याय सामाजिक न्याय के मतलब होला समाज में सामाजिक विशेषाधिकार प्राप्त वर्ग के अभाव आ कवनो नागरिक के सङे जाति, धरऽम, रङ, लिंङ आ जनमस्थान के आधार पर भेदभाव ना कइल। भारत समाज से हर किसिम के शोषण के खतम करे खातिर खड़ा बा। ( ii ) आर्थिक न्याय आर्थिक न्याय के मतलब होला आवक, संपऽति आ आर्थिक स्थिति के आधार पर मरद मेहरारू के बीच कवनो भेदभाव ना कइल। ई संपऽति के समान रूप से वितरण, आर्थिक समानता, उत्पादन आ वितरण के साधन सभ पर एकाधिकार नियंत्रण के अन्त, आर्थिक संसाधनन के विकेंद्रीकरण, आ सभके रोजी-रोटी के कमाई खातिर पर्याप्त मोका के सुनिश्चित करे खातिर खड़ा बा। ( iii ) राजनीतिक न्याय राजनीतिक न्याय के मतलब होला जनता के राजनीतिक प्रक्रिया में भागीदारी खातिर समान, स्वतंत्र आ निष्पक्ष अवसर। ई बगेर भेदभाव के सगरी जनता के बरोबर राजनीतिक अधिकार देवे खातिर खड़ा बा। [[भारत के संबिधान|भारतीय संविधान]] में उदारवादी लोकतंत्र के प्रावधान बा जवना में सभ जनता के अधिकार बा, आ भाग लेवे के आजादीओ बा। ===स्वतंत्रता=== स्वतंत्रता के अवधारणा भारतीय नागरिकन के काराई पर आजादी के संदर्भ देला। एहमे ई स्थापित कइल गइल बा के भारतीय नागरिकन पर कवनो बेवजह रोक नइखे, के ऊ लोग का सोचत बाडे़, ओह लोग के अभिव्यक्ति के तरीका, आ कवना तरह से ऊ लोग आपन विचारन के काम में आगे बढ़ावल चाहत बाडे़, यद्दपी आजादी के मतलब ई नइखे कि कुछो करे के आजादी बा। एकर प्रयोग संवैधानिक हद में करे के पड़ी। ===समानता === समानता शब्द के मतलब होला समाज के कवनो वर्ग खातिर विशेषाधिकार के अभाव, आ बिना भेदभाव के सभ बेकत के पर्याप्त अवसर उपलब्ध करावल। ===बन्धुत्ता=== ई भाई-बहिन, आ मित्रता के भाव आ राष्ट्र से अप्नाहट के भाव के बोध करावेला। आमुख में घोषणा कइल गइल बा के बन्धुत्ता के दूगो चीज के सुनिश्चित कराने के पड़ी- व्यक्ति के गौरव आ राष्ट्र के एकता आ अखण्डिता। आमुख में 42वा संविधान संशोधन (1976) से ‘अखण्डिता’ शब्द जोड़ल गइल। == संदर्भ == {{Reflist|33em}} [[श्रेणी:भारत के संविधान]] {{India-law-stub}} 6st7xwo599s03iuled2pcuf17jjzrxj टीका (वैक्सीन) 0 76774 803337 781509 2026-07-29T16:21:48Z SM7Bot 7808 /* top */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803337 wikitext text/x-wiki {{about|रोग निवारक दवाई, वैक्सीन|माथ पर लगावल जाए वाला टीका|टीका||टीका (बहुअर्थी)}} {{Infobox medical intervention | name = Vaccine | image = Smallpox vaccine.jpg | caption = [[Smallpox vaccine]] and equipment for administering it | alt = | pronounce = | synonyms = | ICD10 = | ICD9 = | ICD9unlinked = | MeshID = D014612 | LOINC = | other_codes = | MedlinePlus = | eMedicine = }} '''टीका''' चाहे '''भैक्सिन''' ({{Lang|en|Vaccine}}, ''वैक्सीन'') एगो अइसन जैविक तेयारी बाटे जे केवनो संक्रामक रोग से लड़े के सकान देवेला। एगो निसचित रोग के टीका के उहे रोग फइलावे अला जीव के तनी अबर कऽ के चाहे मुआ चाहे ओकरे कोनो भाग से बनावल जाला। टीका मे जोन अबर कइल चाहे मुआइल जीव रहेला, जब देहि मे जाला तऽ देह ओकरा के चिन्ह जाला आ फेर जब ओह रोग के रूप मे असल जीव देहि मे आवेला देहि होकरा से लड़े के सकान राखेला आ ऊ रोग बेकत के ना होखेला।<ref name="pmid26214521">{{cite journal | vauthors = Melief CJ, van Hall T, Arens R, Ossendorp F, van der Burg SH | title = Therapeutic cancer vaccines | journal = The Journal of Clinical Investigation | volume = 125 | issue = 9 | pages = 3401–12 | date = September 2015 | pmid = 26214521 | pmc = 4588240 | doi = 10.1172/JCI80009 }}</ref><ref name="pmid26861670">{{cite journal | vauthors = Bol KF, Aarntzen EH, Pots JM, Olde Nordkamp MA, van de Rakt MW, Scharenborg NM, de Boer AJ, van Oorschot TG, Croockewit SA, Blokx WA, Oyen WJ, Boerman OC, Mus RD, van Rossum MM, van der Graaf CA, Punt CJ, Adema GJ, Figdor CG, de Vries IJ, Schreibelt G | title = Prophylactic vaccines are potent activators of monocyte-derived dendritic cells and drive effective anti-tumor responses in melanoma patients at the cost of toxicity | journal = Cancer Immunology, Immunotherapy | volume = 65 | issue = 3 | pages = 327–39 | date = March 2016 | pmid = 26861670 | pmc = 4779136 | doi = 10.1007/s00262-016-1796-7 }}</ref><ref>{{cite journal |vauthors=Brotherton J |title=HPV prophylactic vaccines: lessons learned from 10 years experience |journal= Future Virology|volume=10 |issue=8 |pages=999–1009 |year=2015 |pmid= |doi=10.2217/fvl.15.60 }}</ref><ref name="pmid24748633">{{cite journal | vauthors = Frazer IH | title = Development and implementation of papillomavirus prophylactic vaccines | journal = Journal of Immunology | volume = 192 | issue = 9 | pages = 4007–11 | date = May 2014 | pmid = 24748633 | doi = 10.4049/jimmunol.1490012 | doi-access = free }}</ref> टीका लगावे के काज के टीकाकरन कहल जाला। छुआवे ठेकाए से होखे अला रोग के रोके ला टीकाकरन सभ से नीमन पाय हऽ। टीकाकरन से चेचक लेखा रोग के दुनिया भ मे खतम कऽ दिहल गइल बा आ पोलियो, [[खसरा रोग|खसरा]] आ टेटनस जस रोग के कम कइल गइल बाटे। हेकरा [[अंग्रेजी]] मे ''भैक्सिन'' कहल जाला। ==असर== बैग्यानिक लोग इ मानेला जे संक्रामक रोग के नस्ट करे ला टीका बहुते कारगर पाए हऽ।<ref>{{cite journal | vauthors = Orenstein WA, Bernier RH, Dondero TJ, Hinman AR, Marks JS, Bart KJ, Sirotkin B | title = Field evaluation of vaccine efficacy | journal = Bulletin of the World Health Organization | volume = 63 | issue = 6 | pages = 1055–68 | date = 1985 | pmid = 3879673 | pmc = 2536484 }}</ref><ref>{{Cite web|url=https://hub.jhu.edu/2017/01/11/vaccines-autism-public-health-expert/|title=The science is clear: Vaccines are safe, effective, and do not cause autism|last=Jan 11|first=Hub staff report / Published|last2=2017|date=2017-01-11|website=The Hub|access-date=2019-04-16}}</ref><ref>{{cite journal | vauthors = Ellenberg SS, Chen RT | title = The complicated task of monitoring vaccine safety | journal = Public Health Reports | volume = 112 | issue = 1 | pages = 10–20; discussion 21 | date = 1997 | pmid = 9018282 | pmc = 1381831 }}</ref><ref>{{Cite web|url=http://www.healthychildren.org/English/safety-prevention/immunizations/Pages/Vaccine-Safety-The-Facts.aspx|title=Vaccine Safety: The Facts|website=HealthyChildren.org|access-date=2019-04-16}}</ref> तबो हेकर परभाव के सीमा बाटे। {{clear}} == संदर्भ == {{Reflist|33em}} == बाहरी कड़ी == {{external media | align = right | width = 300px | video1 = [https://www.youtube.com/watch?v=mq-PgsVY-VU Modern Vaccine and Adjuvant Production and Characterization], [[Gen. Eng. Biotechnol. News|Genetic Engineering & Biotechnology News]] }} * {{curlie|Health/Pharmacy/Drugs_and_Medications/Vaccines_and_Antisera|Vaccines and Antisera}} * [http://www.emro.who.int/entity/vpi/ WHO Vaccine preventable diseases and immunization] * [https://web.archive.org/web/20070615154213/http://www.who.int/immunization/documents/positionpapers/en/ World Health Organization position papers on vaccines] * [http://www.historyofvaccines.org/ The History of Vaccines], from the [[College of Physicians of Philadelphia]] *: This website was highlighted by ''Genetic Engineering & Biotechnology News'' in its "Best of the Web" section in January 2015. See: {{small|{{cite news | title=The History of Vaccines | department=Best of the Web | volume=35 | issue=2 | date=15 January 2015 | page=38 | work=[[Gen. Eng. Biotechnol. News|Genetic Engineering & Biotechnology News]] }}}} [[श्रेणी:वैक्सीन]] [[श्रेणी:मेडिकल रोकथाम]] 76vn71dg65owhnp8ab077om8wfcgoev बफेलो बफेलो बफेलो बफेलो बफेलो बफेलो बफेलो बफेलो 0 76878 803311 754425 2026-07-29T16:20:02Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803311 wikitext text/x-wiki {{italic title}} '''''बफेलो बफेलो बफेलो बफेलो बफेलो बफेलो बफेलो बफेलो''''' अमेरीकी अंग्रेजी के एगो व्याकरन के दृष्टिकोन से सुध वाक्य हऽ। एहमे बफेलो सबद के तीनगो माने हऽ: * एगो संग्या सबद, जेवन अमेरिका के एगो नगर के नांव हटे। * एगो क्रिया, जेकर माने केहू के तंग कइल चाहे हड़काइल होखेला। * एगो जीव, जेकरा [[भोजपुरी]] मे भंइस कहल जाला। ==माने== एह वाक्य के चारि भाग मे बुझल जा सकेला: * बफेलो बफेलो: बफेलो नगर के बफेलो (भंइस) * बफेलो बफेलो बफेलो: बफेलो नगर के बफेलो (भंइस) मे हड़कावत बा। * बफेलो: हड़कावत बा * बफेलो बफेलो: बफेलो नगर के बफेलो (भंइस) के एकर पूरा माने इ भइल कि: बफेलो नगर के बफेलो (भंइस) के बफेलो नगर के बफेलो (भंइस) हड़कावत बा जे उहो बफेलो नगर के बफेलो (भंइस) के हड़कावस। आउर सरल सबद मे: अमेरिका के बफेलो नगर के भंइसा ओही नगर के एगो अउर भइसा के बाकि सभ भईसा के हड़कावे के कहऽता। [[श्रेणी:अंग्रेजी]] {{grammar-stub}} oblk5iq9khmermhacubfb0dvh95thib बनारस मेट्रो 0 77431 803307 734425 2026-07-29T16:19:48Z SM7Bot 7808 /* top */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803307 wikitext text/x-wiki {{Infobox Public transit | name = बनारस मेट्रो | image = | imagesize = | native_name = <!-- [[WP:NOINDICSCRIPT]] --> | locale = [[बनारस]], [[उत्तर प्रदेश]], [[भारत]] | transit_type = रैपिड ट्रांजिट | began_operation = | system_length = {{convert|29.235|km|mi|abbr=on}} | chief_executive = | headquarters = | lines = 2 (प्रस्तावित) | stations = 26 (प्रस्तावित) | website = | ridership = | train_length = 3 कोच | track_gauge = | el = | average_speed = 35 की.मी/प्रति घंटा | top_speed = 90 की.मी/प्रति घटा | owner = उत्तर प्रदेश मेट्रो रेल निगम | operator = | map = | alt = | caption = | area served = | line_number = | start = | end = | annual_ridership = | character = | vehicles = | map_state = }} '''बनारस मेट्रो''' एगो प्रस्तावित रैपिड ट्रांजिट सिस्टम बाटे [[बनारस]], [[उत्तर प्रदेश]] सहर खातिर। प्रस्तावित सिस्टम में दुगो कॉरिडोर भेल (तरना, शिवपुर) ले बनारस हिंदू विश्वविद्यालय तक (19.35 किमी) अउर बेनिया बाघ ले सारनाथ तक (9.885 किमी)। इ परियोजना के संभाव्यता अध्‍ययन राइट्स कइले रहे अउरी 2015 में पूरा कइलस। एकरा में 26 गो स्टेशन सामिल कइल गइल बा, जेमे 20 गो अंडरग्राउंड रही अउर 6 गो उचाई प रही। दुनो कॉरिडोर के लंबाई 29.235 किमी, जेमे 23.467 किमी अंडरग्राउंड रही आ 5.768 किमी उचाई प रही, राज्य सरकार 150 करोड़ रोपया एकरा खातिर दे चुकल बिया फरवरी, 2019 में राज्य के मेट्रो परियोजना खातिर।<ref>{{cite web|url=http://timesofindia.indiatimes.com/city/varanasi/DPR-of-Varanasi-Metro-Rail-Project-approved/articleshow/52526623.cms|title=बनारस मेट्रो रेल परियोजना के डी पी आर मंजूर हो गइल - टाइम्स ऑफ इंडिया|publisher=|accessdate=24 October 2016}}</ref><ref>{{cite web|url=https://www.metrorailnews.in/tag/varanasi-metro-rail-project/|title=बनारस मेट्रो रेल परियोजना - - मेट्रो रेल खबर|publisher=|accessdate=24 October 2016}}</ref><ref>[http://www.business-standard.com/article/economy-policy/kanpur-varanasi-metro-rail-projects-could-start-by-december-2016-116021900701_1.html Kanpur, Varanasi metro rail projects could start by December 2016]</ref><ref>{{cite web|url=https://www.metrorailnews.in/varanasi-metro-centre-approves-varanasi-metro-rail-project-plan/|title=बनारस मेट्रो - केंद्र बनारस के मेट्रो रेल परियोजना मंजूर कइलस - - मेट्रो रेल खबर|publisher=|accessdate=24 October 2016}}</ref> ==इतिहास== उत्तर प्रदेश सरकार [[बनारस]], [[लखनऊ]], [[मेरठ]] आ [[आगरा]] मे मेट्रो चालू करे के बिचरलस।<ref>{{cite web|url=https://www.metrorailnews.in/varanasi-metro-metro-rail-project-awaits-central-govts-approval/|title=Varanasi Metro - Metro rail project awaits central govt's approval - - Metro Rail News|publisher=|accessdate=24 October 2016}}</ref><ref>{{cite web|url=http://timesofindia.indiatimes.com/city/kanpur/Centre-will-approve-Kanpur-metro-project-soon-Venkaiah-Naidu/articleshow/54693472.cms|title=Centre will approve Kanpur metro project soon: Venkaiah Naidu - Times of India|publisher=|accessdate=24 October 2016}}</ref><ref>{{cite web|url=http://economictimes.indiatimes.com/industry/transportation/railways/centre-approves-plan-for-varanasi-and-kanpur-metro/articleshow/52392309.cms|title=Centre approves plan for Varanasi and Kanpur metro - The Economic Times|publisher=|accessdate=24 October 2016}}</ref> ==दाम== एह प लागे वाला अनुमानित रकम ₹17,227 करोड़ बाटे। == संदर्भ == {{Reflist}} rt8bfb8lbso85235ljtrba37qew4a4h रबड़ 0 77522 803367 775290 2026-07-29T16:24:07Z SM7Bot 7808 /* top */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803367 wikitext text/x-wiki '''रबड़''' एगो अइसन सामान होला जेकरा से कुछु लिखल चीझु चाहे दागि के मिटकावल जाला। ई बहुत आकर अउर रंग के होखेला। कोनो कोनो पेंसिल के पाछे एगो छोट रबड़ लागल रहेला। [[File:Office-pink-erasers.jpg|right|thumb|300px|रबड़]] [[File:Benutzung eines Radiergummies.gif|thumb|रबड़ परजोग कइल]] सभ से पहिले रबड़ के पेंसिल से लिखला प होखे वाला भूल के मिटकावे ला भइल रहे। बादि मे सेयाहियो के मिटकावे ला रबड़ बनि गइल। सिलेट चाहे ऊज्जर बोर्ड प लिखल मिटकावे ला परजोग होखे अला समानो के रबड़ कहल जाला। ==इतिहास== [[File:Pencil Eraser.jpg|right|thumb|पेंसिल मे लागल रबड़]] रबड़ से पहिले कुछु मिटकावे ला मोम के परजोग होखत रहे, आ बलुइया पथल के झिटका सभ के परजोग सेयाही से लिखला प होखे अला भूल मे मिटकावे ला होखत रहे। 1770 मे एगो अंग्रेजी इंजिनियर एडवर्ड नैरने पहिला बेरा रबड़ बनइलन। 2vta84rekbxa8g428se0i6lbfn8azrg कटर 0 77525 803309 754452 2026-07-29T16:19:52Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803309 wikitext text/x-wiki [[File:Sharpener with Pencil.jpg|thumbnail|right|upright=1.2|हाथ से इस्तेमाल होखे वाला कटर]] [[File:Apsco Vacuhold pencil sharpener 15.ogv|thumb|right|upright=1.2|मोटर से इस्तेमाल होखे वाला कटर]] '''कटर''' ([[अंग्रेजी]]: पेंसिल शार्पनर चाहे पेंसिल प्वाइंट; [[आयरलैंड]] मे पेरेर भा टॉपर)<ref name="CoughlanHughes2007">{{cite book|author1=Gerry Coughlan|author2=Martin Hughes|title=Irish Language and Culture|url=https://books.google.com/books?id=bhfHeSgnAcQC|year=2007|publisher=Lonely Planet|isbn=978-1-74059-577-3|page=74}}</ref> एगो अऊजार हटे जेकरा पेंसिल के छिल के ओकर नोखि के तेज कइल जाला। एकर संचालन मनई करेला चाहे कोनो मे मोटरो लागल रहेला। कोनो कोनो कटर प ढकना लागल रहेला जेकरा पेंसिल मे छिलकोइया बिगे बजी हटाई दिहल जाला। ==इतिहास== कटर से पहिले पेंसिल के छुड़ी से तेज कइल जात रहे। कटर बनला के बादि इ काज बहुते सरल हो गइल आ पेंसिल बेसी नीमन से छिलाए लागल। कटर बनावे के सुरुआत फ्रांस मे भइल, जब एगो फ्रंसिसी पुस्तक एगो सिए बाउचर के कटर कर आविष्कार दिया बिस्तार से लिखलस। उहां के पैंटोग्राफ प काज करत रहीं आ उहाँ के पेंसिल छिले ला एगो निमन अउजार के दरकार रहे।<ref>{{Cite book|url=https://books.google.de/books?id=Vut7Bt4AjmIC&pg=PA290&lpg=PA290&dq=boucher+taille+crayon&source=bl&ots=lsz_4RhR9w&sig=ACfU3U3I6fQPJYCAgMn8uZCalI7ZnxhQ6A&hl=de&sa=X&ved=2ahUKEwj7ltDRyeHgAhWNJVAKHQ-dBW8Q6AEwCHoECAYQAQ#v=onepage&q=boucher%20taille%20crayon&f=false|title=Recueil de la Société polytechnique: ou Recueil industriel, manufacturier, agricole et commercial, de la salubrité publique, ...|last=|first=|date=1822|publisher=Société polytechnique|year=|isbn=|location=|pages=290-295|language=fr}}</ref><ref name="Grahl">{{Cite web|url=http://patent-infos.de/overview/history-pencil-sharpeners-1850.html|title=History of pencil sharpeners and pointers -1850 Lassimone, Cooper/Eckstein, Lahausse|last=Grahl|first=André|website=patent-infos.de|access-date=2019-03-01|archive-url=https://web.archive.org/web/20191113171647/http://patent-infos.de/overview/history-pencil-sharpeners-1850.html|archive-date=13 November 2019|url-status=dead}}</ref> बाउचर के जंत्र बहुते नीमन रहे आ देस हुनकर एकरा ला देस बिदेस मे तारिफो भइल।<ref>{{Cite book|url=https://books.google.de/books?hl=de&id=Vm9QAAAAcAAJ&dq=allgemeine+maschinen+encyclop%C3%A4die&q=boucher#v=snippet&q=boucher&f=false|title=Allgemeine Maschinen-Encyclopädie: A - Beu|last=Hülsse|first=Julius A.|date=1841|publisher=Voss|year=|isbn=|location=|pages=247|language=de}}</ref> बाकिर बाउचर एपे पटेंट ना लिहलन। बर्नार्ड लेसिमोन कटर के पेटेंट ला 1828 मे अर्जी दिहलन<ref>{{Cite book|url=https://books.google.de/books?id=BkYFAAAAQAAJ&pg=PA383&dq=lassimonne+taille+crayon&hl=de&sa=X&ved=0ahUKEwjx6O6c8eLgAhXQPOwKHSj6BywQ6AEINDAB#v=onepage&q=lassimonne%20taille%20crayon&f=false|title=Description des machines et procédés spécifiés dans les brevets d'invention, publ. par C.P. Molard. [With] Table générale des vingt premiers volumes...|last=|first=|date=1835|publisher=|year=|isbn=|location=|pages=583, 81-83|language=fr}}</ref> एह पेंटेंट प पेरिस के एगो दोकान कटर बेचलस आ बनईलस। 1833 मे इंग्लैंड के कुपर आ एकस्टाइन, स्टीलॉक्सिनन नाम से एगो पेटेंट लेलन, जेमे दुगो रेती नब्बे डिग्री के कोन प लगाई के सिसम के काठ प लगावल रहे।<ref>{{Cite book|url=https://books.google.de/books?id=esA-AAAAcAAJ&pg=PA318&dq=cooper+eckstein+patent+1833&hl=de&sa=X&ved=0ahUKEwjVpffq6eHgAhXJ-yoKHT1uDS4Q6AEIMzAB#v=onepage&q=cooper%20eckstein&f=false|title=Repertory of patent inventions and other discoveries and improvements in arts, manufactures and agriculture|last=|first=|date=1833|publisher=Macintosh|year=|isbn=|location=|pages=318-319|language=en}}</ref><ref>{{Cite book|url=https://books.google.de/books?id=qoqJvkEyCnIC&printsec=frontcover&hl=de#v=onepage&q=185&f=false|title=Mechanic's Magazine, Museum, Register, Journal & Gazette|last=|first=|date=1837|publisher=Knight and Lacey|year=|isbn=|location=|pages=185|language=en}}</ref> ई आजु ले बाँचल सभ से पुरान कटर बा।<ref>{{Cite web|url=https://www.worthpoint.com/worthopedia/antique-early-patent-pencil-sharpener-633650095|title=RARE ANTIQUE EARLY PATENT PENCIL SHARPENER 1830s - COOPER & ECKSTEIN STYLOXYNON|website=Worthpoint|language=en|access-date=2019-03-01|archive-url=https://web.archive.org/web/20190302024601/https://www.worthpoint.com/worthopedia/antique-early-patent-pencil-sharpener-633650095|archive-date=2 March 2019|url-status=dead}}</ref> 1830 आ 1840 के दसक मे पेरिस केह ढेर जाना जइसे, फ्रनकोइस जोसेफ कटर बनावे मे लागल रहें।<ref>{{Cite book|url=https://books.google.de/books?id=Cf7NAAAAMAAJ&pg=PA406&lpg=PA406&dq=lahausse+taille+crayon&source=bl&ots=NwqKUF0Quf&sig=ACfU3U3u4nPI_8chYc224NRbbRsQw-7ikg&hl=de&sa=X&ved=2ahUKEwjXp7C7jeLgAhVRsqQKHQrZAscQ6AEwB3oECAcQAQ#v=onepage&q=lahausse%20taille%20crayon&f=false|title=Bulletin de la Société d'encouragement pour l'industrie nationale|last=|first=|date=1834|publisher=Société d'encouragement pour l'industrie nationale|isbn=|location=|pages=406-407|language=fr}}</ref> ==छबी सभ== <gallery widths="180"> File:CrankPencilShapener.jpg|Hand-cranked planetary sharpener, with cover in place File:Apsco Vacuhold pencil sharpener 13.jpg|Hand-cranked planetary sharpener from 1950–60, with cover removed File:HighEndSharpener.png|Hand-cranked planetary sharpener with spring-driven pencil holder. The small handle behind the crank is the sharpness regulator. File:SchoolPro Electric Pencil Sharpener.jpg|AC-powered electric pencil sharpener. The mechanism is similar to the planetary sharpener, except there is a motor instead of a crank. File:PencilSharpenerBatteryOp.jpg|Battery-operated sharpener </gallery> ==सनर्भ== qf4z3sk3gbtb2fdj6sb13mhb2wu0ya6 हाथ धोवल 0 77534 803331 754485 2026-07-29T16:21:26Z SM7Bot 7808 /* top */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803331 wikitext text/x-wiki {{Infobox medical intervention | name = हाथ धोवल | synonym = | image = OCD handwash.jpg | caption = साबुन आ पानी से हाथ धोवल | alt = | pronounce = | specialty = | synonyms = | ICD10 = | ICD9 = | ICD9unlinked = | CPT = | MeshID = | LOINC = | other_codes = | MedlinePlus = | eMedicine = }} '''हाथ धोवल''' ([[अंग्रेजी]]: Handwashing) एगो काज हऽ जेमे हाथ के साबुन आ पानी चाहे खाली पानी से साफ कइल जाला, जेकरा से [[वायरस]], बैक्टीरिया चाहे हाथ के मइल हट जाला। हाथ सुखाइलो एकरे हिस्सा मानल जाला एहसे की भींजल हाथ बड़ी हाली दोबेरा लेटा सकेला।<ref name="CDC2020Wash2" /><ref name="Huang2012" /> [[बिस्व स्वास्थ संगठन]] नाहीयो तऽ 20 सेकंड ले हाथ धोवे के सलाह देवेला। हई सभ काम करे के पहिले आ बादि मे हाथ धोवे के चाहीं:<ref>https://www.youtube.com/watch?v=3PmVJQUCm4E</ref> * कोनो बेमरिया के सेवा करे से पहिले आ बादि मे * खायका पकावे से पहिले, पकावत बजी आ बादि मे * खाए से पहिले * टोइलेट के परयोग करे के बादि * नाक झड़ला, खोखला, छींकला के बादि * पसू सभ के छुवला के बादि * घूरा छुवला के बादि जदि हाथ मे धुर गरदा नइखे तऽ पानी के बदल सैनिटाइजर के परजोग कइल जा सकेला।<ref>{{cite web |title=Coronavirus Disease 2019 (COVID-19) |url=https://www.cdc.gov/coronavirus/2019-ncov/prevent-getting-sick/disinfecting-your-home.html |website=Centers for Disease Control and Prevention |language=en-us |date=11 February 2020}}</ref><ref name="Centers for Disease Control">{{cite web |title=When and How to Wash Your Hands|author= Centers for Disease Control |url=https://www.cdc.gov/handwashing/when-how-handwashing.html |website=cdc.gov |language=en-us |date=2 April 2020}}</ref> जदि साबुन आ सैनिटाइजर दूनो नइखे तऽ हाथ के राख अउरी पानी से धोवल जा सकेला, बाकिर कोसिस होए के चाहीं जे साबुन चाहे सैनिटाइजर के परजोग कइल जाव। साबुन से दिन मे बेरि बेरि हाथ धोवला से ढेर बेमारी से बाचल जा सकेला जइसे डाइरिआ आ कलेरा। एकरा से खोखी चाहे [[कोविड-19|कोरोना]] बेमारी से भी बाचल जा सकेला। ==सनर्भ== qlmnqw7z7r2fb5tk46v3d76cq3oqyic बियोनसे 0 77567 803359 777022 2026-07-29T16:23:28Z SM7Bot 7808 /* top */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803359 wikitext text/x-wiki {{Infobox musical artist | image = Beyonce - The Formation World Tour, at Wembley Stadium in London, England.jpg | name = बियॉन्से नोवेल्स | image_size = 220px | caption = बियॉन्से नोवेल्स 2019 मे | background = solo_singer | birth_name = बियॉन्से गज़ेल नोवेल्स | alias = साशा फ़िर्स<!--NOTE TO EDITORS: Sasha Fierce is the name of Knowles' alter-ego. This name therefore is an alias per [[Template:Infobox Musical artist#Alias]], please do not add any other nicknames.--> | birth_date = {{birth date and age|mf=yes|1981|9|4}}<ref name="Born to be a Star">{{cite news |url=http://www.independent.co.uk/news/people/profiles/beyonc-born-to-be-a-star-1890924.html |last=ऐडम्स |first=गाय |title=बियॉन्से: बॉर्न टु बि अ स्टार |work=द इंडिपेंडंट |location=युके |date=फ़रवरी 6, 2010 |accessdate=जनवरी 9, 2011 |archive-url=https://web.archive.org/web/20180617220026/https://www.independent.co.uk/news/people/profiles/beyonc-born-to-be-a-star-1890924.html |archive-date=17 जून 2018 |url-status=live }}</ref> | origin = होस्टन, टेक्सास, अमेरिका | instrument = आवाज़ | genre = आराइंडबी, [[पॉप]], सोल, हिप हॉप | occupation = गायिका, रेकॉर्ड निर्माता, अभिनेत्री, नर्तक, छायाकार, मॉडल, फ़ैशन डिज़ाइनर | associated_acts = डेस्टिनिज़ चाइल्ड, [[जे-ज़ी]], सोलांजे नॉलेस, कॉने वेस्ट, [[लेडी गागा]] | years_active = 1997–अबतक | label = कोलंबिया | website = {{official URL|www.beyonceonline.com}} <br />[[चित्र:Beyoncé autograph.svg|100px]]<br />बियॉन्से के हस्ताक्षर }} '''बियॉन्से गज़ेल नोवेल्स कार्टर''' (जनम: 4 सितंबर 1981) एगो अमेरिकी गवईया, नर्तकी, अभिनेत्री बाड़ी। संगीत, नृत्य आ फैशन मे हिनकर परभवसाली जोगदान हिनकरा के एगो वैश्विक पहिचान दियवले बाटे। ह्यूस्टन मे जनमल आ पिसैल, बियोनसे लइकाइं मे बहुते गावे आ नाचे के प्रतिजोगिता मे भाग लिहले रही। 1990 के दसक के अंत मे ई परसिद्ध भइली। डेस्टिनिज़ चाइल्ड के बाद बियोनसे आपन एकल एल्बम ''डेंजरस्ली इन लव'' 2003 मे अनली जेहमे "क्रेज़ी इन लव" आ "बेबी बॉय" गीत ''बिलबोर्ड'' हॉट 100 मे पहीलका स्थान प रहल। ई एल्बम बछर कर सभसे सफ़ल एल्बम रहल आ एकरा से हुनकरा पांच गो ग्रैमी पुरस्कार मिलल। 2005 मे डेस्टिनिज़ चाइल्ड के टुटला के बादि बियोनसे आपन दोसरका एकल एल्बम ''ब'डे'' 2006 मे रिलीज़ कइली जेमे हिट गीत "इर्रिप्लेसेबल" आ "ब्युटिफ़ुल लायर" रहे। हुनकर तिसरका एकल एल्बम ''आय एम... साशा फ़िर्स'' 2008 मे आइल जेकरा मे चार गो व्यापारिक तौर प सफ़ल गाना &mdash;"इफ़ आय वर अ बॉय", "सिंगल लेडिज़ (पुट अ रिंग ऑन इट)", "हेलो" आ "स्विट ड्रिम्स" रहे। इ एलबम हुनकरा छव गो ग्रैमी पुरस्कार दियवइलस आ एगो राति मे सभसे बेसी ग्रैमी पुरस्कार जिते वाला पहिलका मेहरारू बनि गइली।<ref>{{cite web|last=सिसारिओ|first=बेन|url=http://www.nytimes.com/2010/02/02/arts/music/02grammys.html|title=ऐट हॉट कॉन्टेस्ट, कूल बियॉन्से इज़ अ डायनामो बट नेवर अ डिवा|work=द न्यु यॉर्क टाइम्स|publisher=द न्यु यॉर्क टाइम्स कंपनी|date=फ़रवरी 1, 2010|accessdate=फ़रवरी 1, 2010|archive-url=https://web.archive.org/web/20100211040834/http://www.nytimes.com/2010/02/02/arts/music/02grammys.html|archive-date=11 फ़रवरी 2010|url-status=live}}</ref> तिन बरस बादि बियोनसे आपन चउथा एकल एल्बम "4" (2011) रिलीज़ कइली जे हूनकरा ''बिलबोर्ड'' 200 के पहिलका पायदान प रहे आला लगातार चउथा अल्बम रहल। हेकरा से ऊ दोसरका मेहरारू कलाकार आ तिसरका कलाकार बनि गइली जेकर चार गो एल्बम चार्ट के पहिलका क्रमांक प रहल। संगित काज के बादि बियोनसे अभिनय कला मे अइली। उ 2001 मे बनल संगितमय फिलिम ''कार्मेन: अ हिप हॉपेरा'' से पदार्पण कइली आ बादि मे ''ऑस्टिन पॉवर्स इन गोल्डमेम्बर'' (2002) आ ''ड्रिमगर्ल्स'' (2006) खानी बड फ़िल्मन मे काज कइली जेकरा ला हुनकरा के दुगो [[गोल्डन ग्लोब पुरस्कार|गोल्डन ग्लोब पुरस्कारों]] ला नामांकन मिलल। उ 2008 मे बनन ''कैडिलैक रेकॉर्ड्स'' मे अभिनय किया। 2004 मे बियोनसे आ हुनकर माई मिलके आपन पारिवारिक फ़ैशन शृंखला हाउस ऑफ़ डेरिऑन चालू कइल। 2010 मे उ लोग ''[[फ़ोर्ब्स]]'' के बिश्व कर 100 सभसे बेसी बरियार आ प्रभावपुर्ण गायकन के लिस्ट पहिलका स्थान प<ref>{{cite web |url=http://www.forbes.com/2010/06/22/beyonce-lady-gaga-business-entertainment-celeb-100-10-musicians_slide.html |title=सेलेब्रिटी 100: म्युज़िशियन्स |work=फ़ोर्ब्स |date=जुन 29, 2010 |accessdate=जुन 29, 2010 |archive-url=https://www.webcitation.org/65iBE8cnq?url=http://www.forbes.com/2010/06/22/beyonce-lady-gaga-business-entertainment-celeb-100-10-musicians_slide.html |archive-date=25 फ़रवरी 2012 |url-status=live }}</ref> आ होकर बिश्व कर 100 सभसे बेसी बरियार आ प्रभावपुर्ण सिलेब्रिटी के लिस्ट मे दुसरका स्थान प रहली।<ref>{{cite web |author=डोरोथी पोमेरांट्ज़, लेसी रोज़ |url=http://www.forbes.com/2010/06/22/oprah-winfrey-lady-gaga-twilight-business-entertainment-celeb-100-10-intro.html |title=द वर्ल्ड्स मोस्ट पावरफ़ुल सेलेब्रिटिज़ |work=फ़ोर्ब्स |date=जुन 28, 2010 |accessdate=जुन 29, 2010 |archive-url=https://web.archive.org/web/20120113145629/http://www.forbes.com/2010/06/22/oprah-winfrey-lady-gaga-twilight-business-entertainment-celeb-100-10-intro.html |archive-date=13 जनवरी 2012 |url-status=live }}</ref> बियोनसे के काज हुनकरा कए गो पुरस्कार आ मान दियवले बा जेकरा मे 16 गो ग्रैमी पुरस्कार, 11 गो एमटीवी वीडियो म्युज़ीक पुरस्कार, एगो बिलबोर्ड मिलेनियम पुरस्कार आ हॉलिवुड वॉक ऑफ़ फ़ेम मे डेस्टिनिज़ चाइल्ड संघे एक सितारा बा। 2009 मे ''बिलबोर्ड'' हुनकरा 2000 के दशक कर उच्च रेडियो कलाकार का मन देलस।<ref name="Female artist of the decade">{{cite web|url=http://www.billboard.com/charts-decade-end/hot-100-songs?year=2009&begin=41&order=position#/charts-decade-end/radio-songs-artists?year=2009|title=टॉप 100 म्युज़िक हिट्स, टॉप 100 म्युज़िक चार्ट्स, टॉप 100 सांग्स ऐंड द हॉट 100|work=बिलबोर्ड|date=दिसंबर 31, 2009|accessdate=जुन 6, 2010|archiveurl=https://archive.ph/20120720052938/http://www.billboard.com/charts-decade-end/hot-100-songs?year=2009&begin=41&order=position%23/charts-decade-end/radio-songs-artists?year=2009#/charts-decade-end/radio-songs-artists?year=2009|archivedate=2012-07-20|url-status=live}}</ref> मई 2010 ले बियोनसे [[अमेरिका]] मे 1.2 करोड़ एल्बम की बेचले रही। जनवरी 2012 ले हुनकर 7.5 करोड रेकॉर्ड बिश्वभर मे बेचाइल रहे।<ref name="Newstateman">{{cite web |url=http://www.newstatesman.com/global-issues/2009/09/world-fashion-gay-india-church |title=द 50 पिपल हू मैटर टुडे: 41–50 |work=न्यू स्टेट्समैन |location=युके |date=सितंबर 24, 2009 |accessdate=फ़रवरी 7, 2011 |archive-url=https://web.archive.org/web/20090928040553/http://www.newstatesman.com/global-issues/2009/09/world-fashion-gay-india-church |archive-date=28 सितंबर 2009 |url-status=live }}</ref> ==जिनगी आ कैरियर== ===सुरुआती जिनगी आ कैरियर=== बियॉन्से गज़ेल नोवेल्स, के जनम 4 सितंबर 1981 के [[ह्यूस्टन]], [[टेक्सास]] मे [[सेलेस्टिन "टीना" नोवेल्स]] (née Beyonce), जे हेयरड्रेसर आ सैलून के मालिक रही, आ [[मैथ्यू नोवेल्स]], जे [[जेरोक्स]] के बिक्री प्रबंधक रहन, के घरे भइल; टीना [[लुइसियाना क्रियोल]] हई, आ मैथ्यू [[अफिरकी अमेरिकी]] हवन। बियॉन्से के छोट बहिन [[सोलेंज नोवेल्स|सोलेंज नोवेल्सो]] एगो गवइआ हई आ [[डेस्टिनीज चाइल्ड]] ला बैकअप डांसर रहल बाड़ी। सोलेंज आ बियॉन्से पहिली बहिन बाड़ी जिनहन के नंबर वन एल्बम रहल। बियॉन्से के नाना-नानी, लुमास बियॉन्से आ एग्नेज डेरेओन (ओडिलिया ब्रौसार्ड आ यूजीन डिरूएन के बेटी) फ्रेंच भासी लुइसियाना क्रियोल रहल लोग, जिनहन के जड़ [[न्यू आइबेरिया]] मे रहल। बियॉन्से के [[क्रियोल लोग|क्रियोल]] मानल जाला, जवन की इनकर अजो-आजी लोग रहल। अपना महतारी के देने से, बियॉन्से फ्रांस के दक्खिन-पच्छिम के कय गो फ्रांसीसी कुलीन लोग के बंसज बाड़ी, जेकरा मे 9वीं सदी से ले के विस्कॉन्ट डी बेअर्न के परिवार आ विस्कॉन्ट डी बेलजुंस के परिवारो बा। ई [[जीन-विंसेंट डी अब्बाडी डी सेंट-कास्टिनो|जीन-विंसेंट डी अब्बाडी डी सेंट-कास्टिन]] के बनज हई, जे एगो फ्रांसीसी धनी बेकत आ सैन्य नेता रहन जे एकेडिया मे अंगरेज सभ के खिलाफ मूल निवासी अबेनाकी के संगे लड़ाई लड़ले रहन, आ एकेडियन नेता जोसेफ ब्रौसार्ड ओ के। इनकर चउथा परदादी मैरी-फ्रांसोइस त्राहन के जनम 1774 मे [[फ्रांस]] के बेले आइल पऽ बसल बैंगोर मे भइल। त्राहन एकेडियन लोग के बेटी रही जे ब्रिटिस देस निकाला के बाद बेले आइ के सरन लेले रहली। एस्टेट्स ऑफ ब्रिटनी बेले आइल के जमीन सभ 78 गो एकेडियन परिवार आ पहिले से बसल निवासी लोग के बीचे बखरा लगा दिहले रहे। त्राहन परिवार दस बरिस से ढेर समय ले बेले आइल पऽ रहल आ एकरे बाद ऊ लोग लुइसियाना मे आप्रवास कइल, जहाँ इनके बियाह एगो ब्रौसार्ड के वंशज से भइल। बियॉन्से उनके वंश पर शोध कइली आ ​​पता चलल कि ऊ एगो गुलाम मालिक के वंशज हवे जे उनके गुलाम से बियाह कइले रहल। इनकर महतारीओ दूर के यहूदी, स्पेनिश, चीनी आ इंडोनेशियाई मूल के हई। बियॉन्से के कैथोलिक रूप से पोसल गइल बा आ ह्यूस्टन के सेंट मैरी मोंटेसरी स्कूल से इनकर पढ़ाई भइल बा, जहाँ ऊ नाच के कक्षा मे नाम लिखववली। इनकर गवइ के खोज तब भइल जब नाच सिखावे वाली डार्लेट जॉनसन एगो गीत गावे लगली आ ऊ एकरा के खतम कइली, उहो ऊपर के सुर लगा के। सात साल के उमिर मे स्कूल टैलेंट शो जीतला के बादो बियॉन्से के संगीत आ परफार्मेंस मे रुचि जारी रहल, शो मे जॉन लेनन के "इमैजिन" गा के बियॉन्से 15/16 बरिस के लइकन के हरा दिहली। 1990 के दसक के अंत मे, बियॉन्से ह्यूस्टन के एगो संगीत स्कूल पार्कर एलिमेंटरी स्कूल मे नाम लिखववली जहाँ ऊ स्कूल के गवइअन संगे गावत रही। एकरे अलावा ऊ हाई स्कूल फॉर द परफॉर्मिंग एंड विजुअल आर्ट्स मे पढ़ाई कइली आ बाद मे एलिफ एल्सिक हाई स्कूलो मे पढ़ाई कइली। बियॉन्से सेंट जॉन यूनाइटेड मेथोडिस्ट चर्च मे दू साल ले एकलवादक के रूप मे गायन दल के सदस्यो रही। जबे बियॉन्से आठ बरिस के रहली तबे उनकर भेंट लाटाविया रॉबर्सन से ऑल-गर्ल एंटरटेनमेंट ग्रुप के एगो ऑडिशन मे भइल। एह लोग के तीन गो अउरी लइकिन साथे गर्ल्स टाइम नाँव के एगो ग्रुप मे राखल गइल, आ ई लोग ह्यूस्टन के टैलेंट शो सर्किट पर रैप आ डांस कइलस। समूह के देखला के बाद आर एंड बी निर्माता अर्ने फ्रेगर ई लोग के अपना नॉर्दर्न कैलिफोर्निया स्टूडियो मे ले अइले आ स्टार सर्च मे रखलें जे ओह घड़ी के नेशनल टीवी पऽ सभसे बड़ टैलेंट शो रहे। गर्ल्स टाइम जिते ना पइलस आ बाद मे बियॉन्से कहली कि ऊ लोग जवन गीत पेस कइलस ऊ नीमन ना रहे 1995 मे बियॉन्से के बाबू एह समूह के प्रबंधन करे ला अपना नौकरी छोड़ि दिहलें। एकरा से बियॉन्से के परिवार के आमदनी आधा हो गइल अउरी उनकर माइ-बाबू के आपन घर अउरी गाड़ी बेच के अलग-अलग अपार्टमेंट मे रहे के पड़ल। मैथ्यू मूल लाइन-अप के चार कर दिहलें आ ई समूह अन्य स्थापित आर एंड बी गर्ल ग्रुप सभ खातिर एगो ओपनिंग एक्ट के रूप में परफार्म करत रहल। ई लइकियन के रिकार्ड लेबल सभ से पहिले ऑडिशन दिहल गइल आ अंत मे इलेक्ट्रा रिकार्ड्स मे साइन कइल गइल, कुछ समय खातिर अटलांटा रिकार्ड्स में आ गइलें आ अपना पहिली रिकार्डिंग पर काम कइलें, बाकी कंपनी द्वारा इनहन के काट दिहल गइल। एहसे परिवार पऽ अउरी तनाव पैदा हो गइल अउरी बियॉन्से के माइ-बाबू अलग हो गइल लोग। 5 अक्टूबर 1995 के ड्वेन विगिन्स के ग्रास रूट्स एंटरटेनमेंट एह समूह के साइन कइलस। 1996 मे ई लइकी लोग सोनी म्यूजिक संगे समझौता के तहत आपन पहिला एल्बम रिकार्ड कइल शुरू कइलस, नोल्स परिवार फेर से एक हो गइल, एह समूह के कोलंबिया रिकार्ड्स के साथ अनुबंध मिलल। ===1997-2002: डेस्टिनिज चाइल्ड=== {{Main|डेस्टिनिज चाइल्ड}} [[File:Destiny's Child Tour.jpg|thumb|left|डेस्टिनिज चाइल्ड के अंतिम लाइन-अप मे बियॉन्से (बीचे), 2005 के डेस्टिनी फुलफिल... एंड लविन इट संगीत कार्यक्रम मे परफार्म करत]] ==सनर्भ== {{Singer-stub}} 6h7kk13jc25c9p782kv6k58tdadfbt4 क्रेयोन शिनचैन 0 78722 803351 722533 2026-07-29T16:22:50Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803351 wikitext text/x-wiki '''क्रेयोन शिनचैन''' ([[जापानी भाषा|जापानी]]: クレヨンしんちゃん, Hepburn: Kureyon Shin-chan), जेकरा '''शिनचैन''' नांव से जानल जाला, योशितो उसुई के लिखल एगो जापानी मांगा हऽ। एमे शिन्नोसुके "शिनचैन" नोहारा नांव के एगो पाँच बरिस के लइका, ओकर पिता,माई, बहिन आ ओकर संघतिया के कहानी हटे। एके जापान के कासुकाबे नगर के देखावल गइल बा। शिनचैन पहिलका बेरा 1990 मे मांगा एक्शन नांव के एगो साप्ताहिक मैगज़ीन मे छपल रहे। योशितो उसुई के मुअला के चलते असल मांगा के 11 सितंबर 2009 के बन करे के पड़ल। नयका मांगा 2010 मे न्यू क्रेयोन शिनचैन नांव से चालू भइल (新クレヨンしんちゃん, Shin Kureyon Shin-chan)। एकर एनिमे बनल 1992 मे चालू भइल जे आज ले चलऽता आ तीस से बेसी भासा मे आ चुकल बा। ई 45 देसन मे देखावल जा चुकल बा, आज ले 1007 गो एपिसोड आ 26 गो फिलिम बनल बा। अभी ले 27.5 करोड़ गो मांगा बेचाइल बा। ==परिचय== [[File:Tobu Crayon Shin-Chan train 2016-12-18.jpg|thumb|जापान के एगो रेलगाड़ी जेपे क्रेयोन शिनचैन बनल बा]] एकर असल मे ढेर सभ मजाक चाहे चुटकुला अइसन बा जेमे सही भासा के परजोग नइखे कइल गइल। एहिसे अइसन लोग जे जापानी ना जाने उ लोग एकर मजाक के बुझ ना पावेला। कोनो कोनो के तऽ दोसर भासा मे अनुवादो ना कइल जा सके। कुछो निश्चित वाक्य चाहे कहाउत लगभग हरमेशा कुछो काम के संगे होला; एमे से कए गो वाक्य सभ के निश्चित जबाब होला। एकरे चलते अइसन देखे के मिलेला जे शिनचैन कए बेरा आपन माई बाबू के लगे ओह घड़ी के हिसाब से गलती वाक्य के परजोग करेला; जइसे की; जब शिनचैन घरे आवेला तऽ "Welcome Back" (भोजपुरी: स्वागत बा; जापानी: おかえりなさい; "okaeri nasai") बोलेला; बलुक ओकरा "I am home" (भोजपुरी: हम घरे आ गइनी; जापानी: "ただいま" "Tadaima") बोले के चाहीं। अनुबाद करे मे एगो अउरी परेसानी ओनोमेटोपोइक (अइसन शब्द जेकर उच्चारन ओकर माने बतावेला; जइसे घड़ी के आवाज खाती टिक टिक शब्द) जापानी शब्दन के उपजोग से जनमेला। कुछू अउर हसे वाला बात जे श्रृंखला मे दोहरावल जाला, ऊ सभ अधिक सार्वभौमिक (सभ जगहा एके लेखा) गुन के होला, जइसे अइसन काम सभ जेकरा से लोग हसे (जैसे चॉपस्टिक से बरफ खाइल) चाहे, लइका होला के बादो, सेयान लेखा बोलल। बाकिर तबो, बहुते सभ चिझ के बुझ के सराहे ला जापानी संस्कृति आ/चाहे भासा के समझ होखे के चाहीं; जइसे की, शिनचैन के "मिस्टर एलीफैंट", भले इ बुझा जाला बाकिर, एकर संबंध एगो लइकन सभ के जापानी गीत "ज़ो-सान" (ぞ さ さ ん) से बा। शिनचैन हमेसा अपना से बड़ सुन्नर लइकी सभ के देख के पगला जाला, आ एकरा मे बेसी हसि तब आवेला जब शिनचैन छोट लइका लेखा ई लोग के लोभावे के कोसिस करेला, जइसे कि ऊ लोग से पूछ के (खराब ढंग से) "का तोहरा शिमला मिर्च पसन बा?" (ー き マ ー 好 ー?)। शिनचैन अपना से बड़ लोग से नीमन से ना बतियावे, जइसे की, खिसियाह लोग से पूछ के की "पुलिस लगे तू कतना बेरा गइल बाड़ऽ?" चाहे बुढ़ लोग से पूछ के की "तू कय बरिस के बाड़ऽ?"। [[श्रेणी:जापानी मांगा]] 8zl0c50ddjrykpdz2rdqt09d2yvy0ob एलिज़ाबेथ ऑलसेन 0 78730 803302 752761 2026-07-29T16:07:00Z SM7Bot 7808 /* top */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803302 wikitext text/x-wiki {{Infobox person | name = एलिज़ाबेथ ऑलसेन | image = File:Elizabeth Olsen1 (cropped).jpg | caption = एलिज़ाबेथ ऑलसेन | birth_name = एलिज़ाबेथ चेस ऑलसेन | birth_date = {{Birth date and age|1989|2|16}} | birth_place = [[लॉस एंजिलिस]], अमेरिका | alias = लिज्जी ऑलसेन | alma_mater = न्यू यॉर्क विश्वविद्यालय | occupation = अभिनेत्री | years_active = 1993–अबहिन ले | partner = | relatives = }} '''चार्ल्स एलिज़ाबेथ ऑलसेन''' (जनम: फरवरी 16, 1989) एगो अमेरिकी अभिनेत्री हई। 2011 मे आइल इनकर फिलिम "मार्था मर्सी मे मारलिन" से इनकरा नाम मिलल, आ एकरा ला इनकरा के कए गो पुरस्कार मिलल। बादि मे ई साइलेंट हाउस (2011), लीबरल आर्ट्स (2012), ओल्डब्वॉय (2013), गॉडजिला (2014), आई सॉ दी लाइट (2015), विंड रिवर (2017) आ इंग्रिड गोज वेस्ट (2017) फिल्म सभ में काज कइली। टेलीविज़न प, ई ''फेसबुक वॉच'' के ड्रामा सीरीज़ '''सॉरी फॉर योर लॉस''' (2012-2019) मे अभिनय किया कइली, जेकरा ला इनकरा क्रिटिक्स चॉइस टेलीविज़न अवार्ड ला नामांकित कइल गइल। [[श्रेणी:1989 में जनम]] [[श्रेणी:अमेरिकी फिलिम ऐक्ट्रेस]] [[श्रेणी:जियत लोग]] {{Actor-stub}} bqxzc31h042jswm5zm6ah6towaevx9u गरेना फ्री फायर 0 80250 803369 799254 2026-07-29T16:24:18Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803369 wikitext text/x-wiki {{Infobox video game | title = गरेना फ्री फायर | image = | developer = 111डोट स्टुडियो | publisher = गरेना इंटरनेशनल प्राइवेट लिमिटिड | director = | producer = | designer = | engine = युनिटी | platform = एन्ड्रॉयड<br />माइक्रोसॉफ्ट विंडोज़<br />आइओएस<br />एक्सबॉरक्स वन<br />प्लेस्टेशन <br /> | release = 4 दिसंबर, 2017 | genre = बैटल रॉयल|बैटल गेम | modes = मल्टीप्लेयर }} '''गरेना फ्री फायर''', जेकरा के '''फ्री फायर (FF)''' भी कहल जाला, एगो बैटल रॉयल गेम हवे जे गरेना द्वारा एंड्रॉयड आ आईओएस खातिर बिकसित आ प्रकाशित कइल गइल। ई 2019 में वैश्विक स्तर पर सभसे ढेर डाउनलोड होखे वाला मोबाइल गेम बन गइल। 2021 ले, फ्री फायर 15 करोड़ दैनिक सक्रिय प्रयोगकर्ता लोग के पार क चुकल रहे।<ref>{{Cite web |last=Obedkov |first=Evgeny |date=2021-08-17 |title=Mobile battle royale Free Fire surpasses 150 million peak daily active players |url=https://gameworldobserver.com/2021/08/17/garena-free-fire-surpasses-150-million-peak-daily-active-players |access-date=2022-10-14 |website=Game World Observer |language=en-US}}</ref><ref>{{Cite web |title=Garena Free Fire Overtakes PUBG Mobile as the Top Grossing Mobile Battle Royale Game in the U.S. |url=https://sensortower.com/blog/state-of-mobile-battle-royale-q1-2021 |access-date=2022-10-14 |website=sensortower.com}}</ref> फ्री फायर मैक्स, फ्री फायर के ग्राफिक रूप से बढ़ावल संस्करण, 28 सितंबर 2021 के वैश्विक स्तर पर रिलीज भइल।<ref>{{Cite news|date=28 September 2021|title=Garena Free Fire Max is now available: How to download, supported platforms, features and more|work=[[The Times of India]]|url=https://timesofindia.indiatimes.com/gadgets-news/garena-free-fire-max-is-now-available-how-to-download-supported-platforms-features-and-more/articleshow/86587488.cms|access-date=2 October 2021}}</ref><ref>{{Cite web |date=2021-09-22 |title=Free Fire MAX: Release date, pre-registration details, exclusive features |website=One Esports |url=https://www.oneesports.gg/free-fire/free-fire-max-game-release-date/ |access-date=2022-10-17 |language=en-US}}</ref> == मालिक == फॉरेस्ट ली फ्री फायर के निर्माता गैरेना के संस्थापक हवें आ ई गेम बनावल उनकर बिचार रहे। एकरे नतीजा बा कि फॉरेस्ट ली के एह खेल के मालिकाना हक बा। फॉरेस्ट ली वर्तमान में अपना कंपनी गैरेना के [[अध्यक्ष]] आ सीईओ के रूप में काम करे लें। == खेल == फ्री फायर थर्ड पर्सन परिप्रेक्ष्य में खेलल जाला। जइसे कि अउरी शूटिंग गेम सभ में, खिलाड़ी जॉयस्टिक के इस्तेमाल से किरदार के हिला सके ला आ फायर बटन से आइटम सभ के गोली मार सके ला आ फेंक सके ला। गेम में मौजूद पात्र कूद सकेलें, रेंगत हो सकेलें आ लेट सकेलें. जब लड़ाई के मैदान में होखे तब खिलाड़ी लोग "ग्लू वॉल" नाँव के ग्रेनेड के इस्तेमाल कवर के रूप में क सके ला ताकि नुकसान ना होखे। === गेम के मोड === '''बैटल रोयाल मोड (Battle Royale Mode)''' में अधिकतम 52 खिलाड़ी बिना हथियार के एगो बेतरतीब द्वीप पर उतर सकेलें। सभके एक दूसरा के खतम क के जिए के पड़ेला, जवना में हथियार अउरी सामान के इस्तेमाल कइल जाला जवन कि इमारत में मिलेला। खिलाड़ी लोग के एह मोड के सोलो, या 2 या 4 खिलाड़ी के टीम में खेले के विकल्प बा। अगर खिलाड़ी एह मोड के रैंकिंग में खेलेलें त एकर असर ओह लोग के रैंक पर पड़ी. जबकि क्लासिक मोड सिर्फ मस्ती अउरी अभ्यास खाती बा। फ्री फायर में छह गो बैटल रोयाल मैप बाड़ें ऊ बाड़ें बरमुडा (Bermuda), बरमूडा रिमास्टरड(Bermuda Remastered), कलाहारी (Kalahari), परगेटारी (Purgatory), अल्पाइन(Alpine), आ नेक्सटेरा(NeXTerra)<ref>{{Cite web |title=The Next Garena Free Fire Map Is Called NeXTerra, Launching August 20 |url=https://www.gamespot.com/articles/the-next-garena-free-fire-map-is-called-nexterra-launching-august-20/1100-6506565/ |access-date=2022-11-14 |website=GameSpot |language=en-US}}</ref>. '''क्लैश स्क्वाड (Clash Squad)''' 4 बनाम 4 मोड के बा। एह मोड में खिलाड़ी लोग गेम के भीतर के पइसा के इस्तेमाल तइयारी के समय में कौनों दुकान से हथियार आ अउरी सामान खरीदे खातिर करे ला, आ फिर बिरोधी टीम से लड़ाई करे ला। बेस्ट ऑफ 7 मोड के रूप में खेल चार से सात राउंड तक चलेला। एगो दौर तब जीतल जाला जब एक टीम के सभ 4 गो खिलाड़ी बाहर हो जालें, या त दुसरा टीम द्वारा या फिर पर्यावरण के नुकसान के कारण। 2022 के हिसाब से ई गेम बैटल रोयाल मोड के समान नक्शा सभ के इस्तेमाल करे ला। क्लैश स्क्वाड के रैंकिंग आ क्लासिक दुनु के रूप में खेलल जा सकेला.<ref>{{Cite web |last=Thakkar |first=Nishant |title=What is the Clash Squad in Free Fire? |url=https://www.sportskeeda.com/free-fire/what-clash-squad-free-fire |access-date=2022-10-17 |website=www.sportskeeda.com |language=en-us}}</ref> '''लोन वुल्फ (Lone Wolf)''' या त क्लैश स्क्वाड मोड के 1 बनाम 1 या फिर "ड्यूओ" (2 बनाम 2) संस्करण हवे जेह में अउरी जटिल नियम बाड़ें। हर पक्ष के अगिला 2 राउंड में इस्तेमाल होखे वाला उपकरण चुने के मौका मिली। राउंड बेस्ट ऑफ 5 खेलल जाला। अगर दुनु टीम 4-4 राउंड जीतले बा त आखिरी दौर के शुरुआत दुनु टीम के अपना पसंद के हथियार चुने के आजादी से होखी. सभ खिलाड़ी दौर के शुरुआत से ही डिफ़ॉल्ट रूप से बनियान अउरी हेलमेट जईसन बेसिक आइटम हासिल करेले। ई मोड अपना खुद के नक्शा में होला जेकरा के "लोहा गुंबद(Iron Dome)" कहल जाला।<ref>{{Cite web |date=2021-11-28 |title=Garena Free Fire: Lone Wolf Mode coming! A new weapon strategy to go live too |url=https://tech.hindustantimes.com/gaming/news/garena-free-fire-lone-wolf-mode-coming-a-new-weapon-strategy-to-go-live-too-71638080044854.html |access-date=2022-10-13 |website=HT Tech |language=en}}</ref> '''क्राफ्टलैंड(Craftland)''' खिलाड़ियन के गेम में बिल्डर के इस्तेमाल से आपन फ्री फायर मैप बनावे देला। क्राफ्टलैंड फीचर के मदद से खिलाड़ी अपना बनावल नक्शा के बना सकतारे, साझा क सकतारे अउरी ओकरा संगे खेल सकतारे। ई एडवांस कस्टमाइजेशन खातिर स्क्रिप्ट एडिटिंग के सपोर्ट करे ला जेह से रचनाकार लोग के अपना बिचार सभ के साथ काम करे के ढेर आजादी मिले ला। खिलाड़ी लोग नक्शा में बिल्डिंग, गेमप्ले आइटम, आ सजावट के तरीका अपना तरीका से जोड़ सके ला जबले कि जगह के सीमा अधिकतम स्तर ना हो जाव। दू गो नक्शा बा जवना के इस्तेमाल खिलाड़ी आपन डिजाइन बनावे में कर सकेलें। <ref>{{Cite web |last=Biswas |first=Apurba |date=2021-09-20 |title=Garena Free Fire Max Craftland: Create Your Own Battlefield & Invite Your Friends to Play, More Details - Inside Sport India |url=https://www.insidesport.in/garena-free-fire-max-craftland-create-your-own-battlefield-invite-your-friends-to-play-more-details/ |access-date=2022-10-14 |website=www.insidesport.in |language=en-US |archive-date=2022-10-14 |archive-url=https://web.archive.org/web/20221014141643/https://www.insidesport.in/garena-free-fire-max-craftland-create-your-own-battlefield-invite-your-friends-to-play-more-details/ |url-status=dead }}</ref><ref>{{Cite web |last=Forst |first=Sarah |date=2022-05-23 |title=Free Fire's OB34 Craftland Stuns Gamers Like Never Before! |url=https://techwafer.com/free-fires-ob34-craftland-stuns-gamers-like-never-before/ |access-date=2022-10-14 |website=TechWafer |language=en-US |archive-date=2022-10-14 |archive-url=https://web.archive.org/web/20221014141643/https://techwafer.com/free-fires-ob34-craftland-stuns-gamers-like-never-before/ |url-status=dead }}</ref> === कैरेक्टर सिस्टम === फ्री फायर में 50+ कैरेक्टर के कैरेक्टर सिस्टम बा। हर पात्र के एगो अलगे कौशल होला जवन सक्रिय भा निष्क्रिय हो सकेला. सक्रिय कौशल के गेम में बटन से मैन्युअल रूप से ट्रिगर कइल जा सके ला आ निष्क्रिय कौशल के स्वचालित रूप से ट्रिगर कइल जाला। हर पात्र पात्र के कौशल आ अन्य पात्रन से 3 गो कौशल समेत 4 गो कौशल से लैस कर सकेला (एक गो सक्रिय कौशल आ तीन गो निष्क्रिय कौशल के एकही पात्र से लैस कइल जा सकेला)। फ्री फायर के पात्रन के फ्री फायर यूनिवर्स में अनोखा कहानी बा। एह में से कुछ किरदार वास्तविक जीवन के सेलिब्रिटी लोग से प्रेरित रहलें जिनहन के साथे फ्री फायर के सहयोग रहल जइसे कि क्रिस्टियानो रोनाल्डो(Cristiano Ronaldo) के साथ सहयोग से क्रोनो(Chrono)<ref>{{Cite web |last=Rossel |first=John Dave |title=Garena Free Fire Officially Announced Collaboration with Cristiano Ronaldo in Operation Chrono Update |url=https://afkgaming.com/mobileesports/news/5723-garena-free-fire-officially-announced-collaboration-with-cristiano-ronaldo-in-operation-chrono-update |access-date=2022-10-27 |website=AFK Gaming |language=en}}</ref>, डीवीएलएम(DVLM) के साथ सहयोग से दिमित्री(Dimitri) एंड थिवा(Thiva)<ref>{{Cite web |first=Rahul |last=Majumdar |date=2021-08-20 |title=Garena Free Fire 4th Anniversary Missions, Rewards and Celebration Song ‘Reunion’ Revealed |url=https://in.ign.com/garena-free-fire-winterlands/164998/news/garena-free-fire-4th-anniversary-missions-rewards-song-reunion-revealed |access-date=2022-10-27 |website=IGN India |language=en-in}}</ref>, एनिटटा(ANITTA) के सहयोग से ए-पट्रोआ(A-Patroa)<ref>{{Cite web |last=Negreira |first=Camila |date=2022-07-01 |title=Anitta’s Free Fire character will be available on July 2 for free |url=https://dotesports.com/mobile/news/anittas-free-fire-character-available-on-july-2-for-free |access-date=2022-10-27 |website=Dot Esports |language=en-US |archive-date=2022-10-27 |archive-url=https://web.archive.org/web/20221027023929/https://dotesports.com/mobile/news/anittas-free-fire-character-available-on-july-2-for-free |url-status=dead }}</ref> , डीजेआलोक(DJ Alok) के सहयोग से एलोक(ALOK) और जस्टिन बेबर(Justin Beiber) के साथे सहयोग से आ जेबेइब्स(JBeibs)<ref>{{Cite web |title=Justin Bieber Is Performing And Debuting A New Song In Garena Free Fire |url=https://www.gamespot.com/articles/justin-bieber-is-performing-and-debuting-a-new-song-in-garena-free-fire/1100-6505148/ |access-date=2022-11-11 |website=GameSpot |language=en-US}}</ref> के साथे। === इन-गेम ईवेंट्स === फ्री फायर एगो वीडियो गेम हवे जेह में बिबिध किसिम के इन-गेम इवेंट सभ के सामिल कइल जाला। ई इवेंट या त पेड हो सके लें या फिर फ्री आ खिलाड़ियन के गेम में इनाम आ बोनस कमाए के मौका देला। पेड इवेंट, जइसे कि एलिट पास (अब 55 सीजन<ref>{{Cite web|url=https://gamesroid.com/free-fire-all-elite-pass-bundle-list|title=Free Fire Elite Passes: Overview Of All 55 Seasons|date=1 April 2022|website=GamesRoid.com|language=en-GB|access-date=30 September 2022}}</ref> के बाद बैटल पास के जगह ले लिहल गइल) आ डायमंड रोयाल, खिलाड़ी लोग के एक्सक्लूसिव कॉस्मेटिक्स के अनलॉक करे के इजाजत देला या वर्चुअल करंसी के इस्तेमाल से बिसेस आइटम भा बोनस खरीद सके ला। दूसर ओर मुफ्त इवेंट सभ में आमतौर पर सभ खिलाड़ियन के उपलब्धता होला आ एह में भाग लेवे खातिर कौनों भुगतान के जरूरत ना पड़े ला। एह इवेंट सभ में गेम में बिसेस चुनौती भा सीमित समय के इवेंट सामिल हो सके लें जेह में खिलाड़ियन के बिसेस इनाम आ बोनस कमाए के मौका मिले ला। एह आयोजनन के विषय अलग अलग मौका पर अलग अलग हो सकेला. == परिसर == फ्री फायर के आधार के बिस्तार से एनिमेटेड शॉर्ट्स आ अउरी सामग्री सभ में बतावल गइल बा जे गैरेना द्वारा एह गेम के प्रचार खातिर प्रकाशित कइल गइल बा। फ्री फायर यूनिवर्स(Free Fire Universe) आधिकारिक ब्रह्मांड हवे जहाँ फ्री फायर गेम, कॉमिक्स आ कहानी (फ्री फायर टेल्स) होला। हर फ्री फायर किरदार के कहानी आ पारिवारिक पृष्ठभूमि अलग होला आ फ्री फायर यूनिवर्स में कहानी से पात्र एक दुसरा से जुड़ल बाड़ें। ब्रह्मांड में तीन गो शहर बा जवना के नाम बा LA LUNA, GRIZA, आ NEW DAWN जहाँ हर पात्र रहेला. फ्री फायर के पात्रन के दू गो अंश में बाँटल गइल बा HORIZON आ MAMBAS, HORIZON शहर के शासक अंश हवे जहाँ अमीर आ ताकतवर लोग बा जबकि MAMBAS HORIZON के खिलाफ एगो मुक्ति समूह हवे। साल 2022 में फ्री फायर फ्री फायर यूनिवर्स के पहिली छोट फिलिम "हाउ टू स्टार्ट ए फायर(How to Start a Fire)" रिलीज कइलस जेह में फ्री फायर के एगो किरदार हयातो यागामी(Hayato Yagami) के देखावल गइल। == अगवानी == एह ग्राफिक्स के "मिड आ लो स्पेसिफिकेशन वाला फोन खातिर फायदा" बतावल गइल, बाकी एगो समीक्षक के कहनाम बा कि "अगर बढ़िया ग्राफिक्स वाला गेम आपके चीज बा त हमनी के सलाह ना देत बानी जा कि रउआ फ्री फायर बैटलग्राउंड खेलीं। लेकिन अगर रउआ पसंद बा।" बैटल रॉयल गेम आ अपना दोस्तन के साथे मस्ती करे के चाहत बानी त रउरा जरूर खेले के चाहीं." टेक्टुडो (Techtudo) के ताईस कारवाल्हो (Tais Carvalho) टिप्पणी कइलें कि फ्री फायर "परफार्मेंस के प्राथमिकता देला, जेकरा चलते ई कवनो भी प्रकार के डिवाइस खातिर एगो बढ़िया विकल्प बा। गेमप्ले अलगा से खड़ा बा आ मनोरंजन खातिर पर्याप्त सामग्री बा आ फायदेमंद लड़ाई पेश करे खातिर।" किरदार आ कौशल के प्रगति का बारे में ऊ कहली कि ई एगो चमकदार ऐड-ऑन ह। <ref>{{Cite web|last=Carvalho|first=Taís|date=5 March 2018|title=Free Fire ou Rules of Survival: veja qual o melhor Battle Royale|url=https://www.techtudo.com.br/noticias/2018/03/free-fire-ou-rules-of-survival-veja-qual-o-melhor-battle-royale.ghtml|access-date=7 January 2021|website=TechTudo|language=pt-br}}</ref> गूगल प्ले के सालाना "बेस्ट ऐप ऑफ द ईयर" के लिस्ट में फ्री फायर 2019 के "बेस्ट पॉपुलर वोट गेम" श्रेणी में जीतल, ब्राजील आ थाईलैंड में सभसे ढेर सार्वजनिक रूप से वोट कइल गइल। गरेना फ्री फायर सभसे लोकप्रिय बैटल रॉयल मोबाइल गेम सभ में से एक हवे, PUBG मोबाइल, फोर्टनाइट बैटल रोयाल आ कॉल ऑफ ड्यूटी: मोबाइल के पीछे। ई खासतौर पर लैटिन अमेरिका, भारत आ दक्खिन-पूरबी एशिया में लोकप्रिय बा। फ्री फायर 2018 के चउथा तिमाही में [[गूगल प्ले स्टोर]] पर चउथा सभसे ढेर डाउनलोड भइल गेम रहल आ 2018 में दुनिया भर में आईओएस आ गूगल प्ले स्टोर पर एक साथ चउथा सभसे ढेर डाउनलोड भइल गेम रहल। ई टाइटिल 2018 में लगभग 182 मिलियन डाउनलोड कइलस, जेकरा चलते ई दुसरा सभसे ढेर डाउनलोड कइल गइल बैटल रॉयल मोबाइल गेम (फोर्टनाइट बैटल रॉयल से ऊपर आ खाली PUBG मोबाइल के पीछे) बन गइल, आ दिसंबर 2018 ले लगभग 19.3 मिलियन डॉलर के महीना के आमदनी भइल, जेकरा चलते ई एगो महत्वपूर्ण बित्तीय सफलता बन गइल गरेना के ह। अगस्त 2021 तक ले, गरेना फ्री फायर के गूगल प्ले स्टोर पर एक अरब से ढेर डाउनलोड भइल बा। Q1 2021 के अंत में, फ्री फायर अमेरिका में राजस्व में PUBG मोबाइल के पीछे छोड़ दिहलस, जवना से 10 करोड़ डॉलर के कारोबार भइल जबकि PUBG मोबाइल के 68 मिलियन डॉलर के कारोबार भइल। पिछला साल के एही अवधि का मुकाबले फ्री फायर के आमदनी में साढ़े चार गुना के उछाल आइल.<ref name = "IGN1">{{cite web | url = https://www.ign.com/m/google/68894/news/the-best-games-e-applications-de-2018-sec-o-google | title = The best games and apps of 2018 according to Google  | date = 3 December 2018 | access-date = 26 September 2019}}{{dead link|date=August 2021|bot=medic}}{{cbignore|bot=medic}}</ref><ref>{{Cite web | url = https://www.mobilegamer.com.br/2018/12/free-fire-eo-melhor-game-de-2018-on-popular-vote-in-some-countries.html | title = Free Fire is the best popular vote game of 2018? Only in some countries! | work = Mobile Gamer | date = 3 December 2018 | access-date = 26 September 2019 }}{{Dead link|date=October 2019 |bot=InternetArchiveBot |fix-attempted=yes }}</ref> नवंबर 2019 ले फ्री फायर के दुनिया भर में 1 बिलियन डॉलर से ढेर के कमाई हो चुकल रहे। आगे चल के ई 2.13 बिलियन डॉलर के साथ 2020 के चउथा सभसे ढेर कमाई करे वाला वीडियो गेम बन गइल आ फिर 2021 के आठवाँ सभसे ढेर कमाई करे वाला मोबाइल गेम बनल जेह में 1.2 बिलियन डॉलर के कमाई भइल, कुल मिला के 2021 ले कम से कम 4.33 बिलियन डॉलर के कमाई भइल। 2020 आ 2021 में फ्री फायर के ईस्पोर्ट्स अवार्ड में ईस्पोर्ट्स मोबाइल गेम ऑफ द ईयर के अवार्ड मिलल। == ईस्पोर्ट् == === फ्री फायर वर्ल्ड सीरीज़ === फ्री फायर 2019 में "'''फ्री फायर वर्ल्ड सीरीज (FFWS)'''" ईस्पोर्ट्स प्रतियोगिता शुरू कइलस। 2020 में कोविड-19 महामारी के कारण FFWS के जगह क्षेत्रीय इवेंट के एगो सिलसिला, "फ्री फायर कॉन्टिनेंटल सीरीज" (FFCS) ले लिहल गइल। फरवरी 2021 में गैरेना 2 मिलियन डॉलर के इनाम के पूल के साथ फ्री फायर वर्ल्ड सीरीज (FFWS) के घोषणा कइलें।<ref>{{Cite news|date=31 May 2021|title=Phoenix Force Lifts The Winner's Trophy at Finals of Free Fire World Series 2021|work=[[News 18]]|url=https://www.news18.com/news/sports/phoenix-force-lifts-the-winners-trophy-at-finals-of-free-fire-world-series-2021-3795965.html|access-date=16 September 2021}}</ref> साल 2021 में, फ्री फायर वर्ल्ड सीरीज इतिहास में सभसे ढेर देखल जाए वाला ईस्पोर्ट्स इवेंट बन गइल जेह में 54 लाख से ढेर पीक लाइव दर्शक लोग रहल आ ई लीग ऑफ लेजेंड्स वर्ल्ड चैम्पियनशिप के पीछे छोड़ दिहलस जेह में 51 लाख पीक व्यूअर रहलें।<ref>{{Cite web |first=Adrian |last=Lai |date=2021-06-03 |title=Free Fire World Series 2021 Singapore Smashes Global Viewership Record |url=https://sea.ign.com/free-fire-android/172569/news/free-fire-world-series-2021-singapore-smashes-global-viewership-record |access-date=2022-10-17 |website=IGN Southeast Asia |language=en-sg}}</ref><ref>{{Cite web |last=Daniels |first=Tom |date=2021-12-22 |title=Top 10 highest viewed esports events of 2021 |url=https://esportsinsider.com/2021/12/highest-viewed-esports-events-2021 |access-date=2022-10-21 |website=Esports Insider |language=en-GB}}</ref><ref>{{Cite web |last=Esguerra |first=Tyler |date=2022-11-06 |title=Worlds 2022 broke the tournament's peak viewership record by a huge margin |url=https://dotesports.com/league-of-legends/news/worlds-2022-broke-the-tournaments-peak-viewership-record-by-a-huge-margin |access-date=2022-11-13 |website=Dot Esports |language=en-US}}</ref> === क्षेत्रीय आयोजन === "'''EMEA इनविटेशनल'''" यूरोप, मध्य पूर्व आ अफिरका खातिर 2021 में फ्री फायर वर्ल्ड सीरीज के जगह रहल। एह चैम्पियनशिप में कुल 12 गो टीम शामिल रहली सँ. 200,000 डॉलर के पुरस्कार पूल के संगे।<ref>{{Cite web |date=2021-11-24 |title=Free Fire EMEA Invitational Finals 2021: Everything you need to know |url=https://esportsgen.com/free-fire-emea-invitational-finals-2021-everything-you-need-to-know-about-the-event/ |access-date=2022-10-15 |website=EsportsGen |language=en-US |archive-date=2022-10-15 |archive-url=https://web.archive.org/web/20221015034207/https://esportsgen.com/free-fire-emea-invitational-finals-2021-everything-you-need-to-know-about-the-event/ |url-status=dead }}</ref> फ्री फायर एशिया चैम्पियनशिप एशिया खातिर 2021 में फ्री फायर वर्ल्ड सीरीज के जगह रहल। एह चैम्पियनशिप में वियतनाम, इंडोनेशिया, थाईलैंड, [[भारत]], [[ताइवान]], पाकिस्तान, आ एमसीपी (मलेशिया, कंबोडिया, आ पाकिस्तान) के कुल 31 गो टीम शामिल रहली सँ. टीमन के मुकाबला चार लाख डॉलर के पुरस्कार पूल खातिर बा.<ref>{{Cite web |date=2021-11-18 |title=Free Fire Asia Championship (FFAC) Play-ins will commence on 20 November! |url=https://esportz.in/free-fire-asia-championship-ffac-play-ins-will-commence-on-20-november |access-date=2022-10-15 |website=esportz |language=en |archive-date=2022-10-15 |archive-url=https://web.archive.org/web/20221015034202/https://esportz.in/free-fire-asia-championship-ffac-play-ins-will-commence-on-20-november |url-status=dead }}</ref> लिगा ब्राजीलेरा डी फ्री फायर (LBFF) ब्राजील में मुख्य फ्री फायर ईस्पोर्ट्स इवेंट हवे। 3 गो डिवीजन, सीरीज ए, बी, आ सी से बनल ई साल में दू गो स्टेज में खेलल जाला आ अभिजात वर्ग फ्री फायर वर्ल्ड सीरीज खातिर क्वालीफाई करे ला।<ref>{{Cite web |title=Garena anuncia Liga Brasileira de Free Fire e Mundial 2020 no Brasil |url=https://www.techtudo.com.br/noticias/2020/01/garena-anuncia-liga-brasileira-de-free-fire-e-mundial-2020-no-brasil-esports.ghtml |access-date=2022-10-15 |website=TechTudo |language=pt-br}}</ref> == विवाद == जनवरी 2022 में, [[पबजी|"PUBG"]] के डेवलपर '''Krafton''', Garena आ एकरे मूल कंपनी '''Sea''' के खिलाफ कॉपीराइट उल्लंघन के मुकदमा दायर कइलें। मुकदमा में Garena पर आरोप लगावल गइल बा कि ऊ अपना गेम गैरेना फ्री फायर आ फ्री फायर मैक्स में इन-गेम आइटम, गेम मैकेनिक्स आ पबजी: बैटलग्राउंड्स आ पबजी मोबाइल के समग्र रूप आ फीलिंग के नकल कइले बाड़न. क्राफ्टन के अनुसार, "फ्री फायर आ फ्री फायर मैक्स बैटलग्राउंड्स के कई पहलु सभ के व्यापक रूप से कॉपी करे ला, अलग-अलग आ संयोजन दुनों में, जवना में बैटलग्राउंड्स के कॉपीराइट वाला अनोखा गेम ओपनिंग 'एयर ड्रॉप' फीचर, गेम के संरचना आ खेल, हथियार सभ के संयोजन आ चयन, कवच, आ बिसेस चीज, लोकेशन आ रंग योजना, सामग्री आ बनावट के समग्र चुनाव।" <ref>{{Cite web|title=PUBG’s developer is suing Apple, Google, and the developer of lucrative PUBG lookalike Free Fire|url=https://www.theverge.com/2022/1/13/22882796/pubg-developer-krafton-suing-apple-google-garena-free-fire-max|access-date=2022-02-16|website=www.theverge.com|language=en}}</ref> 14 फरवरी 2022 के '''भारत सरकार के इलेक्ट्रॉनिक्स आ सूचना प्रौद्योगिकी मंत्रालय''' 53 गो अउरी ऐप के साथे गरेना फ्री फायर पर रोक लगा दिहलस जवन भारत के सूचना प्रौद्योगिकी अधिनियम, संविधान के धारा '''69ए''' के तहत भारत के निजता आ सुरक्षा खातिर खतरा पैदा कइलस।<ref>{{Cite web|title=India bans over 50 more Chinese apps including Garena Free Fire|url=https://social.techcrunch.com/2022/02/13/india-ban-china-apps/|access-date=2022-02-15|website=TechCrunch|language=en-US|archive-date=2022-08-19|archive-url=https://web.archive.org/web/20220819215134/https://techcrunch.com/2022/02/13/india-ban-china-apps/|url-status=dead}}</ref><ref>{{Cite web|date=2022-02-19|title=Garena Free Fire Ban In India: Most Users Can Still Play Free Fire Game Despite Government Ban|url=https://www.news18.com/news/tech/garena-free-fire-ban-in-india-most-users-can-still-play-free-fire-game-despite-government-ban-4788092.html|access-date=2022-02-23|website=News18|language=en}}</ref> == संदर्भ == {{reflist}} == बाहरी कड़ी == ;ऑफिशियल * [http://ff.garena.com/index/en/ आधिकारिक वेबसाइट] {{Webarchive|url=https://web.archive.org/web/20220707174024/https://ff.garena.com/index/en/ |date=2022-07-07 }} * [https://ff.garena.com/universe/en/ फ्री फायर यूनिवर्स वेबसाइट] । । । । । {{वीडियोगेम-आधार}} [[श्रेणी:बीडियोगेम]] [[श्रेणी:2017 के वीडियो गेम]] [[श्रेणी:बैटल रॉयल गेम]] [[श्रेणी:एंड्रॉयड (ऑपरेटिंग सिस्टम) के खेल]] [[श्रेणी:आईओएस गेम]] [[श्रेणी:मल्टीप्लेयर वीडियो गेम]] bgicu86dnyq68rcl7ier8hs0ujku0y9 मूरभूत कर्तव्य 0 87592 803361 789499 2026-07-29T16:23:37Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803361 wikitext text/x-wiki '''मूरभूत कर्तव्यन''' के सौसे [[भारत के लोग|भारतीय नागरिकन]] के मूरभूत फरज के रुप मे व्याख्यायित कइल गइल बा के, देशभक्ति के भावना के प्रोत्साहित करऽस आ भारत के एकता अखण्डित राखऽस। [[भारतीय संविधान|संविधान]] के भाग चार - (क)<ref>{{cite web |title=List of Fundamental Duties- Part IVA, Article 51A |url=https://pendulumedu.com/general-awareness/what-are-fundamental-duties |website=PendulumEdu |language=en-us}}</ref> में निर्धारित ई कर्तव्य व्यक्ति आ राष्ट्र से संबंधित बा। [[नीति निर्देशक सिद्धांत]]<ref>{{cite web |title=Profile - Directive Principles Of State Policy - Know India: National Portal of India |url=https://knowindia.india.gov.in/profile/directive-principles-of-state-policy.php |website=knowindia.india.gov.in |publisher=}}</ref> के जइसे एह के अदालत तबले लागू नइखे कर सकत जबले संसद के कवनो अधिनियम में दोसरा तरह के प्रावधान ना होखे। [[चित्र:Constitution_of_India_(calligraphic)_Cover.png|alt=Constitution of India (calligraphic) Cover|thumb|भारतीय संविधान के ग्रन्थावरण (मूर सुलेख आ प्रकाशित संस्करण)।]] ==इतिहास== नागरिकन के मूरभूत कर्तव्यन के संविधान में 1976 में [[42वा संविधान संशोधन]]<ref>{{cite web |last1=Patil |first1=Amruta |title=42nd Amendment Act |url=https://prepp.in/news/e-492-42nd-amendment-indian-polity-notes |website=prepp.in |publisher=prepp |access-date=Jul 11, 2022}}</ref> में स्वर्ण सिंह समिति<ref>{{cite web |title=Fundamental Dutie's list |url=https://unacademy.com/content/bpsc/study-material/polity/fundamental-duties/ |website=Unacademy}}</ref> के प्रस्ताव पर जोड़ल गइल। ई समिति के निर्माण ओह साल के सुरुआत में सरकार द्वारा कइल गइल रहे। सुरूआत में संविधान में मूर रूप से दसगो के संख्या में मूरभूत कर्तव्य रहे। बाद में 2002 के 86वा संविधान संशोधन द्वारा बढ़ा के ईगारेगो कर देवल गइल। ई ईगारऽगो कर्तव्यन के संविधान के अनुच्छेद 51- क (भाग-IV-A में एकमात्र अनुच्छेद) में सूचीबद्ध बा। [[नीति निर्देशक सिद्धांत]]<ref>{{cite book |title=India 2020: Reference Annual |date=2020 |publisher=Publications Division, Ministry of Information and Broadcasting, Government of India |isbn=978-81-230-3239-9 |url=https://books.google.co.in/books/about/India_2020.html?id=Yh53zQEACAAJ&redir_esc=y |language=en}}</ref> के जैसे मूरभूत कर्तव्यो गैर न्यायसंगत नेत के होला। 2003 में [[भारत के सुप्रीम कोर्ट|सर्वोच्च न्यायालय]] के रंगनाथ मिश्र के फैसला दिहले के, "मूरभूत कर्तव्य के खाली कानूनी रूप से अमल ना होखे के चाही बलुक सामाजिक रूपो से अमल होखे के चाही।" एम्स विद्यार्थी संघ बिरुद्ध एम्स 2001<ref>{{cite web |title=.I.I.M.S. Students Union vs A.I.I.M.S. |url=https://indiankanoon.org/doc/1496684/}}</ref> मामला में सर्वोच्च न्यायालय के कहनामा रहे के, "मूरभूत कर्तव्य [[मूरभूत अधिकार]] के बराबरे महत्वपूर्ण बा।" ==महत्व== मूरभूत कर्तव्य खाली पेडेंट भा तकनीकी नइखे सामाजिक परिवर्तन के कुंजी<ref>{{cite web |title=Fundamental Duties |url=https://www.drishtiias.com/daily-updates/daily-news-analysis/fundamental-duties-3 |website=Drishti IAS |publisher=DAILY UPDATES |language=en}}</ref> के रूप में शामिल कइल गइल रहे। [[समाज]] में अर्थपूर्ण योगदान देवे खातिर नागरिक के सबसे पहिले संविधान आ ओकरा अंगन के बुझे के होई। ई जरूरी बा के लोग एह बेवास्था अउरी एकर बारीकियन, ताकत आ मर्यादा के बुझस। एही से भारत में संवैधानिक संस्कृति के फैलाव करऽल बहुते जरूरी बा<ref>{{cite web |title=मौलिक कर्तव्य |url=https://www.drishtiias.com/hindi/daily-updates/daily-news-analysis/fundamental-duties-3 |website=Drishti IAS |language=en}}</ref>। हरेक नागरिकन के भारतीय लोकतंत्र में अर्थपूर्ण भागीदार बने के जरूरत बा आ संवैधानिक तत्व के ओकरा अस्सल भावना में आत्मसात करे के कोशिश करे के जरूरत बा। मूरभूत कर्तव्यन के उचित एहसास, संपूर्ण संचालन आ अमलीकरण खातिर एगो एकरूप नीति के जरूरत बा जवन नागरिकन के जवाबदेह बनावे में सहैया होखे। मूरभूत कर्तव्य नागरिकन के फरजीयात करेला के, ऊ संविधान समेत भारत के राष्ट्रीय प्रतीकन के सम्मान करस, ओकर धरोहर के सञ्जोवह, ओकर [[वैविध्यपूर्ण संस्कृति]] के संरक्षित करस आ ओकर रक्षा में सहायता करस। एकरे अलावा ई सगरो भारतीयन के बीच भाईचारा के भावना के बढ़ावा देवे, [[पर्यावरण]] आ सार्वजनिक संपत्ति के रक्षा करे, वैज्ञानिक बिचार बिकसित करे, हिंसा से परहेज करे आ जीवन के हर क्षेत्र में श्रेष्ठता के ओरीया प्रयास करे के फरजीयात करे लें। [[राष्ट्रपति]], [[उपराष्ट्रपति]], [[सभापति]], [[संसद सदस्य]], विधानसभा के सदस्य आदि समेत कवनो नागरिक द्वारा संविधान में निगमित मूरभूत कर्तव्य के उल्लंघन के स्थिति में ई संविधान के तिरस्कार ​​समान बा, जे राष्ट्रीय गौरव के अपमान निवारण अधिनियम 1971 के बदे दंडयोग्य बा, [[भारतीय सर्वोच्च न्यायालय|सर्वोच्च न्यायालय]] ई फैसला दिहऽले के ई मूरभूत कर्तव्य [[विधानसभा]] से पारित भइल कवनो कानून के संवैधानिकता के निर्धारितो करे में अदालत के सहायता कर सकेला। अंतर्राष्ट्रीय संधियन में अयीसन कर्तव्यन के संदर्भ बा जैसे के मानवाधिकार के सार्वत्रिक घोषणा आ नागरिक आ राजनीतिक अधिकारन के अंतर्राष्ट्रीय करार आ अनुच्छेद 51 - (क)<ref>{{cite web |last1=Hemant |first1=Singh |title=What is Article 15 of the Indian Constitution? Important Features and Provisions |url=https://www.jagranjosh.com/general-knowledge/article-15-1561544384-1 |website=Jagranjosh.com |access-date=12 may 2020 |date=12 मई 2020}}</ref> में [[भारतीय संविधान]] के एह संधिन के अनुरूप बनावल गइल बा। ==लेखा== हरेक भारतीय प्रजा के ई कर्तव्य होखी के<ref>{{cite web |title=मूल कर्तव्य एक परिचय |url=https://sugamgyan.com/post/fundamental-duties/ |website=सुगम ज्ञान |language=hi-IN |date=15 सितंबर 2018 }}{{Dead link|date=March 2026 |bot=InternetArchiveBot |fix-attempted=yes }}</ref>; 1. संविधान के पालन करस आ संविधान में समायेल आदर्श के आ राष्ट्रीय संस्थानन, [[जन गण मन|राष्ट्रगान]], [[तिरंगा|राष्ट्रपताका]] के सम्मान करस। 2. स्वतंत्रताला आपन [[भारत के आजादी आंदोलन|राष्ट्रीय आंदोलनन]] के प्रेरणा देवेवाला ऊँच आदर्श के सम्मान करस आ अनुसरण करस। 3. भारत के [[संप्रभुता|परमाधिपत्यकता]], एकता आ अखण्डिता के अखण्डरुप से रक्षा करस आ एहर जतन करस। 4. [[भारत|भारतराष्ट्र]] के सुरक्षा करस आ आवाहन कइला पर राष्ट्र के सेवा करस। 5. भारत के सौसे [[भारत के लोग|प्रजाजन]] में समरसता आ समान बन्धुत्ता के भाव के निर्माण करस जौन कवनो [[धर्म|धम्म]], [[भाषा]] आ कवनो प्रदेश चाहे संप्रदाय पर आधारित सगरो भेदभाव से उप्पर होखो। स्त्रीयन के गौरव के अपमानीत करे ऐसन बेवहार के तेज देवस। 6. आपन गौरवशाली परम्परा के आ समृद्ध [[भारतीय संस्कृति|संस्कृतियन]] के मिनऽहा के बुझस आ एह के सहेजस। 7. [[जंगल|जङल]], पोखरन, [[नदी|सरितन]] आ [[जंगली जीव|आरण्य पशुपंछीयन]] समेत कुदरती [[पर्यावरण]] के जतन आ बिकास करस, आऊर जीव - जंतुवन के बदे दायाभाव रखस। 8. वैज्ञानिक आपाधापी, मानवता आ जिज्ञासा आ सुधारशिलता के भावना बिक्सित करस। 9. प्रजात्व संपाति के रक्षा करस आ हिंसा के तिरस्कार करस। 10. बेक्तिगत आ सामूहिक गतिबिधियन के सगरो बिस्तार में निरन्तम् प्रगति के प्रयास करस जेसे राष्ट्र निरन्तम् प्रयत्न आ उपलब्धि के नाया ऊँचाईयन के टो सको। 11. जदि प्रजा माई - बाबू चाहे गार्जियन होखस तऽ छव से चौदऽ बरीस के आपन सबिता चाहे पाल्यसबिता के शिक्षा के तक प्रदान करावस। ==इहो देखल जाय== * [[भारतीय संविधान के आमुख]] * [[भारतीय संविधान]] == संदर्भ == {{Reflist|33em}} 1jpfgbroieqckwdvi1vohjd4vob3omo गुरु तेग बहादुर 0 87743 803372 766293 2026-07-29T16:24:33Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803372 wikitext text/x-wiki {{Infobox religious biography | religion = [[सिख धर्म]] | name = गुरु तेग बहादुर | image = Portrait_of_Guru_Tegh_Bahadur_by_painter_Ahsan.jpg | alt = | caption = बिचला-17वीं-सदी के पोर्ट्रेट, चित्रकार: अहसान | birth_name = त्याग मल | birth_date = {{Birth-date| 21 अप्रैल 1621}} | birth_place = [[अमृतसर]], [[लाहौर सूबा]], [[मुगल राज|मुगल साम्राज्य]] <br /> {{small|(वर्तमान पंजाब, भारत)}} | death_date = {{Death-date and age|24 नवंबर 1675| 1 मई 1621}} | death_place = [[दिल्ली]], [[मुगल राज|मुगल साम्राज्य]] <br /> {{small|(वर्तमान [[भारत]])}} | death_cause = सिर काट के मृत्युदंड | period = 1665–1675 | known_for = * गुरुग्रंथ साहिब के श्लोक * [[औरंगजेब]] के आदेशानुसार मृत्युदंड<ref name="pslf" /><ref>Gill, Sarjit S., and Charanjit Kaur (2008), "Gurdwara and its politics: Current debate on Sikh identity in Malaysia", SARI: Journal Alam dan Tamadun Melayu, Vol. 26 (2008), pages 243–255, Quote: "Guru Tegh Bahadur died in order to protect the freedom of India from invading Mughals."</ref><ref name=cs2013/><ref name=sg2007>{{cite book|last=Gandhi|first=Surjit|title=History of Sikh gurus retold|publisher=Atlantic Publishers|year=2007|isbn=978-81-269-0858-5|pages=653–91}}</ref> * [[आनंदपुर साहिब]] के संस्थापक | spouse = [[माता गुजरी]] | children = [[गुरु गोबिंद सिंह]] | parents = [[गुरु हरगोबिंद]] आ [[माता नानकी]] | predecessor = [[गुरु हरकिशन]] | successor = [[गुरु गोबिंद सिंह]] | image_upright = | battles = [[करतारपुर के लड़ाई]] | native_name = ਗੁਰੂ ਤੇਗ਼ ਬਹਾਦਰ | native_name_lang = pa | native name lang = pa | signature = Authentic signature (neeshan) of Guru Tegh Bahadur.jpg }} [[चित्र:Guru_Teg_bahadur_ji.jpg|thumb|456x456px| [[गुरुद्वारा शीशगंज साहिब|गुरद्वारा सिस गंज साहब]] के आंतरिक दृश्य]] '''गुरु तेग बहादुर''' ( [[पंजाबी भाषा|पंजाबी]] : ਗੁਰੂ ਤੇਗ਼ ਬਹਾਦਰ <small>( गुरमुखी )</small> ; 1 अप्रैल 1621 – 11 नवंबर 1675) <ref>{{Cite web|url=http://www.sikhs.org/guru9.htm|title=The Ninth Master Guru Tegh Bahadur (1621–1675)|website=sikhs.org|archive-url=https://web.archive.org/web/20190107050930/http://www.sikhs.org/guru9.htm|archive-date=7 January 2019|access-date=23 November 2014}}</ref> दस गो गुरु लोग में से नौवाँ गुरु रहलें जे [[सिख धर्म]] के स्थापना कइलें आ 1665 से ले के 1675 में सिर काट दिहला ले सिख लोग के नेता रहलें। इनके जनम भारत के पंजाब के [[अमृतसर]] में 1621 में भइल आ छठवाँ सिख गुरु [[गुरु हरगोबिंद साहब के बा|गुरु हरगोबिंद]] के छोट बेटा रहलें। सिद्धांतवादी आ निर्भीक योद्धा मानल जाए वाला ऊ एगो आध्यात्मिक विद्वान आ कवि रहलें जिनके 115 गो स्तोत्र सिख धर्म के मुख्य ''[[गुरु ग्रंथ साहिब|ग्रंथ श्री गुरु ग्रंथ साहब]]'' में शामिल बाड़ें। गुरु तेग बहादुर के छठवाँ [[मुगल राज|मुगल]] सम्राट [[औरंगजेब]] के आदेश पर भारत के [[दिल्ली]] में मृत्यदंड दिहल गइल। <ref name="bbcgtb">{{Cite web|url=http://www.bbc.co.uk/religion/religions/sikhism/people/teghbahadur.shtml|title=Religions – Sikhism: Guru Tegh Bahadur|publisher=[[BBC]]|archive-url=https://web.archive.org/web/20170414075330/http://www.bbc.co.uk/religion/religions/sikhism/people/teghbahadur.shtml|archive-date=14 April 2017|access-date=20 October 2016}}</ref> सिख पवित्र परिसर [[गुरुद्वारा शीशगंज साहिब|गुरुद्वारा सिस गंज साहब]] आ दिल्ली के [[गुरद्वारा रकाब गंज साहब]] गुरु तेग बहादुर के मूंडी काटल जाये आ दाह संस्कार के जगहन के चिन्हित करेलें। उनकर शहादत के हर साल 24 नवम्बर ''के गुरु तेग बहादुर के शहीदी दिवस के'' रूप में याद कइल जाला। == जीवनी == === शुरुआती जिनगी === गुरु तेग बहादुर छठवाँ गुरु गुरु हरगोबिंद के सबसे छोट बेटा रहलें: गुरु हरगोबिंद के एगो बेटी बीबी वीरो आ पाँच गो बेटा बाबा गुरदित, सूरज माल, अनी राय, अटल राय, आ त्यागा माल भइलें। त्यागा माल के जनम अमृतसर में 1 अप्रैल 1621 के सबेरे भइल रहे। इनके तेग बहादुर (तलवार के पराक्रमी) नाँव से जानल जाए लागल, ई नाँव गुरु हरगोबिंद द्वारा मुगल लोग के खिलाफ लड़ाई में आपन शौर्य देखवला के बाद दिहल गइल। ओह घरी अमृतसर सिख आस्था के केंद्र रहे। सिख गुरु लोग के आसन के रूप में, आ ''मसंद'' भा मिशनरी लोग के कनेक्शन के माध्यम से देश के दूर-दूर के इलाका में सिख लोग से जुड़ाव के साथ, एकरा में सिख धरम के राजधानी के विशेषता के बिकास भइल रहे। गुरु तेग बहादुर के पालन पोषण सिख संस्कृति में भइल रहे आ तीरंदाजी आ घुड़सवारी के प्रशिक्षण मिलल रहे। उहाँ के [[वेद]], [[उपनिषद]], आ [[पुराण]] जइसन पुरान क्लासिक भी सिखावल गइल रहल। तेग बहादुर के बियाह 3 फरवरी 1632 के माता गुजरी से भइल। === बकाला में निवास === 1640 के दशक में गुरु हरगोबिंद आ उनकर मेहरारू नानकी तेग बहादुर आ माता गुजरी के साथे मिल के [[अमृतसर जिला]] के अपना पैतृक गाँव बकाला में आ गइलें। बकाला, जइसन कि ''गुरबिलास दसवीं पातशाही'' में वर्णित बा, तब एगो समृद्ध शहर रहे जवना में कई गो सुंदर कुंड, इनार, आ ''बाओली'' रहे। गुरु हरगोबिंद के निधन के बाद तेग बहादुर अपना पत्नी आ महतारी के संगे बकला में रहत रहले। === गुरु जी के सफर === मार्च 1664 में गुरु हरकिशन के मतहाई (चेचक) हो गइल। जब उनकर अनुयायी लोग पूछल कि उनकरा बाद के के नेतृत्व करी त उ जवाब देले कि ''बाबा बकला'', मतलब कि उनकर उत्तराधिकारी बकला में मिले वाला बा। मरत गुरु के शब्दन में अस्पष्टता के फायदा उठावत बहुत लोग अपना के नया गुरु बतावत बकला में स्थापित हो गइल। एतना दावेदार देख के सिख लोग अचरज में पड़ गइलें। एगो कहानी के मोताबिक, 8वें गुरु के बाद गुरु हरकिशन साहब जी स्वर्ग धाम में जाये के बाद, गुरु के रूप में नकली कई लोग अस्थापित भ गइल रहल। एगो धनी व्यापारी बाबा मखान शाह लबाना, एक बेर आपन नाव के सुरक्षित समुंदर पार करे के प्रार्थना कइले रहले आ वादा कइले रहले कि अगर ऊ बच गइलन त सिख गुरु के 500 सोना के सिक्का उपहार में दे दीहें।<ref name="mk1992" /> उ नौवां गुरु के खोज में पहुंचले। उ एक दावेदार से दूसरा दावेदार तक प्रणाम करत अउरी हर गुरु के दुगो सोना के सिक्का चढ़वले, इ मानत कि सही गुरु के पता चल जाई कि उनुकर मौन वादा रहे कि उ अपना सुरक्षा खाती 500 सिक्का उपहार में दे दिहे। हर "गुरु" से मिलत सोना के दुनु सिक्का स्वीकार क के विदाई देत रहे। <ref name="mk1992" /> तब उनका पता चलल कि तेग बहादुर भी बकला में रहत रहले। लबाना तेग बहादुर के दू गो सोना के सिक्का के सामान्य प्रसाद उपहार में दिहलें। तेग बहादुर उनुका के आपन आशीर्वाद देले अउरी टिप्पणी कइले कि उनुकर प्रसाद वादा कइल पांच सौ से काफी कम बा। मखान शाह लबाना तुरते फरक बूझ के ऊपर भाग गइलन। ऊ छत से चिल्लाए लगले, "गुरु लधो रे, गुरु लधो रे" मतलब "गुरु मिल गइलें, गुरु मिल गइलें"। अगस्त 1664 में एगो सिख संगत बकला पहुंचल आ तेग बहादुर के सिख लोग के नौवाँ गुरु नियुक्त कइलस। ''संगत'' के नेतृत्व गुरु तेग बहादुर के बड़ भाई दीवान दुर्गा मल, उनुका के गुरुत्व प्रदान कइले। जईसे कि मुगल सम्राट जहांगीर के गुरु अर्जन के फांसी दिहला के बाद सिख लोग के बीच रिवाज रहे, गुरु तेग बहादुर के घेरले हथियारबंद अंगरक्षक रहल करें। उ खुद तपस्वी जीवन जीयत रहले। == रचना == गुरु तेग बहादुर ग्रंथ साहब में कई गो भजन के योगदान दिहलें जेह में गुरु ग्रंथ साहब के अंत के लगे के हिस्सा के ''श्लोक'', या दोहा भी सामिल रहलें। गुरु तेग बहादुर मुगल साम्राज्य के बिबिध हिस्सा के दौरा कइलें आ गोबिंद सहली द्वारा महाली में कई गो सिख मंदिर बनावे के कहल गइल। इनके रचना सभ में 116 गो शबद, 15 गो राग आ इनके भगत सभ के 782 गो रचना सभ के श्रेय दिहल जाला जे सिख धर्म में ''बानी'' के हिस्सा हवें। इनके रचना सभ के ''[[गुरु ग्रंथ साहिब|गुरु ग्रंथ साहब]]'' (पन्ना 219-1427) में सामिल कइल गइल बा। एह में कई गो बिसय सभ के सामिल कइल जाला, जइसे कि भगवान के स्वभाव, मानवीय लगाव, तन, मन, दुख, मर्यादा, सेवा, मौत आ मुक्ति। == सफर == गुरु तेग बहादुर पहिला सिख गुरु नानक के शिक्षा के प्रचार करे खातिर [[ढाका]] आ [[आसाम|असम]] समेत देश के अलग-अलग हिस्सा में बहुत घूमले। जवना जगहन पर उहाँ के घूमे आ ठहरल रहले, उहाँ सिख मंदिरन के स्थल बन गइल। अपना यात्रा के दौरान गुरु तेग बहादुर सिख विचार अउरी संदेश के प्रसार कइले, संगही सामुदायिक पानी के कुआं अउरी लंगर (गरीब खाती सामुदायिक रसोई के दान) शुरू कइले। गुरु लगातार तीन बेर कीरतपुर के दौरा कइलेंl 21 अगस्त 1664 के गुरु तेग बहादुर बीबी रूप के पिता गुरु हर राय, सातवाँ सिख गुरु आ उनकर भाई गुरु हरकिशन के निधन पर सांत्वना देवे खातिर ओहिजा गइलें। <ref>{{Cite web|url=https://www.britannica.com/topic/Sikhism/Guru-Har-Rai|title=Sikhism - Guru Har Rai {{!}} Britannica|website=www.britannica.com|language=en|access-date=2021-11-26}}</ref> दूसरा यात्रा 15 अक्टूबर 1664 के भइल, 29 सितंबर 1664 के गुरु हर राय के महतारी बस्सी के निधन के समय। तीसरा यात्रा में उत्तर-पश्चिम भारतीय उपमहाद्वीप के माध्यम से काफी व्यापक यात्रा के समापन भइल| उनकर बेटा [[गुरु गोबिंद सिंह|गुरु गोविंद सिंह]], जे दसवाँ सिख गुरु होखीहें, के जनम [[पटना]] में भइल रहे, जब ऊ 1666 में असम के धुबरी में दूर रहले, जहाँ गुरद्वारा श्री गुरु तेग बहादुर साहब अस्थापित बाटे। उहाँ उ बंगाल के राजा राम सिंह अउरी अहोम राज्य (बाद में असम) के राजा चकरद्वाज के बीच युद्ध के खतम करे में मदद कइलें। उ मथुरा, आगरा, इलाहाबाद अउरी वाराणसी के दौरा कइलें। असम, बंगाल आ बिहार के दौरा के बाद गुरु बिलासपुर के रानी चंपा के दौरा कइलें जवन गुरु के अपना राज्य के जमीन के टुकड़ा देवे के प्रस्ताव रखली। गुरु साहब 500 [[रुपिया|रुपया]] में जगह के खरीद लिहलेंl। उहाँ गुरु तेग बहादुर हिमालय के तलहटी में आनंदपुर साहब शहर के स्थापना कइलें।<ref name="bbcgtb">{{Cite web|url=http://www.bbc.co.uk/religion/religions/sikhism/people/teghbahadur.shtml|title=Religions – Sikhism: Guru Tegh Bahadur|publisher=[[BBC]]|archive-url=https://web.archive.org/web/20170414075330/http://www.bbc.co.uk/religion/religions/sikhism/people/teghbahadur.shtml|archive-date=14 April 2017|access-date=20 October 2016}}<cite class="citation web cs1" data-ve-ignore="true">[http://www.bbc.co.uk/religion/religions/sikhism/people/teghbahadur.shtml "Religions – Sikhism: Guru Tegh Bahadur"]. [[बीबीसी|BBC]]. [https://web.archive.org/web/20170414075330/http://www.bbc.co.uk/religion/religions/sikhism/people/teghbahadur.shtml Archived] from the original on 14 April 2017<span class="reference-accessdate">. Retrieved <span class="nowrap">20 October</span> 2016</span>.</cite></ref> 1672 में तेग बहादुर कश्मीर आ उत्तर-पश्चिम सीमा के रास्ता से होके, जनता से मिले खातिर यात्रा कइलें, काहें से कि गैर-मुसलमान लोग के उत्पीड़न नया ऊंचाई पर पहुँच गइल रहल। == मृत्युदंड == [[चित्र:Head_of_Guru_Tegh_Bahadar_is_brought_to_Anandpur_by_Sikhs.jpg|thumb|गुरु तेग बहदर के सिर के सिख लोग द्वारा आनंदपुर ले आवल जा रहल फ्रेस्को कला]] सिख कथन सभ के प्राथमिक बिबरन ''बचितर नाटक'', गुरु तेग बहादुर के बेटा गुरु गोविंद सिंह के संस्मरण बाटे जे 1680 के दशक के अंत से 1690 के दशक के अंत के बीच के हवे।<ref name=":0">{{Cite journal|last=Fenech|first=Louis E.|date=1997|title=Martyrdom and the Sikh Tradition|url=https://www.jstor.org/stable/606445|journal=Journal of the American Oriental Society|volume=117|issue=4|pages=623–642|doi=10.2307/606445|issn=0003-0279|jstor=606445|archive-url=https://web.archive.org/web/20181006000554/https://www.jstor.org/stable/606445|archive-date=6 October 2018|access-date=2 December 2017}}</ref> गुरु तेग बहादुर के बेटा आ उत्तराधिकारी गुरु के फाँसी के याद कइलें: {{Quote|In this dark age, Tegh Bahadur performed a great act of chivalry (saka) for the sake of the frontal mark and sacred thread. He offered all he had for the holy. He gave up his head, but did not utter a sigh. He suffered martyrdom for the sake of religion. He laid down his head, but not his honor. Real men of God do not perform tricks like showmen. Having broken the pitcher on the head of the king of Delhi, he departed to the world of god. No one has ever performed a deed like him. At his departure, the whole world mourned, while the heavens hailed it as victory.|[[Guru Gobind Singh]]}} तेग बहादुर के फाँसी के अउरी सिख बिबरन, सभ "भरोसेमंद सिख लोग के गवाही" से मिलल होखे के दावा करे लें, लगभग अठारहवीं सदी के अंत में ही उभरल शुरू भइल आ एह तरीका से, अक्सर परस्पर विरोधी बाड़ें। <ref name="sc2001">{{Cite web|url=http://www.thehindu.com/thehindu/2001/10/16/stories/05162524.htm|title=Guru Tegh Bahadur's martyrdom|last=Chandra|first=Satish|website=[[The Hindu]]|archive-url=https://web.archive.org/web/20020228135707/http://thehindu.com/thehindu/2001/10/16/stories/05162524.htm|archive-date=28 February 2002|access-date=20 October 2016}}</ref> इतिहासकार सोहन लाल सूरी के कहनाम बा कि गुरु के दक्षिणी पंजाब में 1672 से 1673 के बीच यात्रा के दौरान सैनिक अउरी घुड़सवार के हजारों अनुयायी मिलल अउरी मुगल प्रतिनिधि के प्रतिरोधक लोगन के आश्रय दिहल गइल। औरंगजेब के एह तरह के गतिविधि के बारे में चेतावल गइल, चिंता के कारण के रूप में जवना से संभवतः विद्रोह हो सकत रहे। फारसी सूत्रन के कहनाम बा कि गुरु एगो डाकू रहलें जिनकर पंजाब के लूटपाट आ बलात्कार के साथे-साथे उनकर विद्रोही गतिविधियन के चलते उनकरा के मृत्यदंड के सजा जल्दी हो गइल। इनके मृत्युदंड के इतिहास बतावे वाला सभसे पहिले के ''फारसी'' स्रोत गुलाम हुसैन खान लगभग 1782 लिखल सियार-उल-मुत्ताखेरिन बाटे, जहाँ तेग बहादुर के (कथित) प्रजा के अत्याचार के कारण औरंगजेब के क्रोध पैदा भइल मानल जाला:<ref name="sc2001">{{Cite web|url=http://www.thehindu.com/thehindu/2001/10/16/stories/05162524.htm|title=Guru Tegh Bahadur's martyrdom|last=Chandra|first=Satish|website=[[The Hindu]]|archive-url=https://web.archive.org/web/20020228135707/http://thehindu.com/thehindu/2001/10/16/stories/05162524.htm|archive-date=28 February 2002|access-date=20 October 2016}}<cite class="citation web cs1" data-ve-ignore="true" id="CITEREFChandra">Chandra, Satish. [https://web.archive.org/web/20020228135707/http://thehindu.com/thehindu/2001/10/16/stories/05162524.htm "Guru Tegh Bahadur's martyrdom"]. ''[[दि हिंदू|The Hindu]]''. Archived from [http://www.thehindu.com/thehindu/2001/10/16/stories/05162524.htm the original] on 28 February 2002<span class="reference-accessdate">. Retrieved <span class="nowrap">20 October</span> 2016</span>.</cite></ref> {{Quote|Tegh Bahadur, the Ninth successor of (Guru) Nanak became a man of authority with a large number of followers. (In fact) Several thousand persons used to accompany him as he moved from place to place. His contemporary Hafiz Adam, a faqir belonging to the group of Shaikh Ahmad Sirhindi's followers, had also come to have a large number of murids and followers. Both these men (Guru Tegh Bahadur and Hafiz Adam) used to move about in Punjab, adopting a habit of coercion and extortion. Tegh Bahadur used to collect money from Hindus and Hafiz Adam from Muslims. The royal ''waqia navis'' (news reporter and intelligence agent) wrote to the Emperor Alamgir [Aurangzeb] of their manner of activity, added that if their authority increased they could become even refractory.|Ghulam Husain|Siyar-ul-Mutakhkherin}} हालांकि सतीश चंद्र हुसैन खान के तर्क के फेस वैल्यू से ना लेवे के चेतावनी देत बाड़े। <ref name="sc2001">{{Cite web|url=http://www.thehindu.com/thehindu/2001/10/16/stories/05162524.htm|title=Guru Tegh Bahadur's martyrdom|last=Chandra|first=Satish|website=[[The Hindu]]|archive-url=https://web.archive.org/web/20020228135707/http://thehindu.com/thehindu/2001/10/16/stories/05162524.htm|archive-date=28 February 2002|access-date=20 October 2016}}<cite class="citation web cs1" data-ve-ignore="true" id="CITEREFChandra">Chandra, Satish. [https://web.archive.org/web/20020228135707/http://thehindu.com/thehindu/2001/10/16/stories/05162524.htm "Guru Tegh Bahadur's martyrdom"]. ''[[दि हिंदू|The Hindu]]''. Archived from [http://www.thehindu.com/thehindu/2001/10/16/stories/05162524.htm the original] on 28 February 2002<span class="reference-accessdate">. Retrieved <span class="nowrap">20 October</span> 2016</span>.</cite></ref> ऊ अलीवर्दी खान के रिश्तेदार रहलें — औरंगजेब के सभसे करीबी विश्वासपात्र लोग में से एक — आ शायद "आधिकारिक औचित्य" देत रहलें। <ref name="sc2001" /><ref>{{Cite web|url=https://en.banglapedia.org/index.php?title=Siyar-ul-Mutakhkherin|title=Siyar-ul-Mutakhkherin – Banglapedia|website=en.banglapedia.org|archive-url=https://web.archive.org/web/20210918151706/https://en.banglapedia.org/index.php?title=Siyar-ul-Mutakhkherin|archive-date=18 September 2021|access-date=2021-09-18}}</ref> {{Efn|Chandra points out a factual error to justify his caution: Adam had died much earlier.}} उपर दिहल कथ्य के अउरी चुनौती भी बाड़ी सऽ। गुलाम हुसैन पंजाब से बहुत दूर रहत रहले। साथे साथ हाफिज आदम से गुरु के संगति कालक्रम में असंगति लिहले बा। तेग बहादुर के गुरु के दर्जा पावे से 21 साल पहिले ई. 1643 में मदीना में हाफिज आदम के निधन हो गइल रहल। आगे इहो बतावल जरूरी बा कि गुलाम हुसैन के मुताबिक तेग बहादुर ग्वालियर में बंद रहले, जहां शाही आदेश के तहत उनुकर लाश के "चार क्वार्टर में काट के" किला के चार गेट प लटका दिहल गइल जबकि इ सभके मालूम बा तेग बहादुर के दिल्ली में फांसी दिहल गइल जहाँ एह घरी सिसगंज गुरुद्वारा बसल बा।{{प्रमाण देईं|date=May 2022}} अठारहवीं सदी के दौरान लिखल गइल सिख साखी सब अप्रत्यक्ष रूप से फारसी स्रोत सभ में कथ्य के समर्थन करे ला; कुछुओ ना कि औरंगजेब के लागू हठधर्मी नीति के जवाब में गुरु "देश के मुस्लिम शासकन के हिंसक विरोध" में रहले। फारसी अउरी सिख दुनों सूत्र एह बात से सहमत बाड़े कि तेग बहादुर सैन्य रूप से मुगल राज्य के विरोध कइले रहले अउरी एह से औरंगजेब के राज्य के दुश्मन के सजा देवे के जोश के मुताबिक मृत्युदंड के निशाना बनावल गइल रहे। कई गो विद्वान लोग एह कथ्य के पहचान निम्नलिखित बा: कश्मीर से हिन्दू पंडित लोग के एगो मंडली औरंगजेब के दमनकारी नीतियन के खिलाफ मदद के निहोरा कइलस, जवना पर गुरु तेग बहादुर ओह लोग के अधिकार के रक्षा करे के फैसला कइलें। मुगल अधिकारियन का तरफ से कश्मीरी ब्राह्मणन पर होखे वाला अत्याचार के सामना करे खातिर तेग बहादुर मखौवाल के निवास से निकल गइलन बाकिर रोपड़ में गिरफ्तार कर के सिरहिंद में जेल में डाल दिहल गइलें चार महीना बाद नवंबर 1675 में उनकरा के दिल्ली ट्रांसफर कइल गइल आ भगवान के नजदीकी साबित करे भा इस्लाम अपनावे खातिर चमत्कार करे के कहल गइल।<ref name="Grewal1998p71" /> गुरु साहब मना कर दिहलन आ उनका साथे धराइल उनकर तीन साथी लोग के उनही के सामने यातना दिहल गइल: भाई माटी दास के टुकड़ा-टुकड़ा में आरी से काटल गइल, भाई दयाल दास के उबलत पानी के कड़ाही में फेंक दिहल गइल, आ भाई सती दास के जिंदा जरा दिहल गइल।<ref name="Grewal1998p71" /> ओकरा बाद लाल किला के नजदीक बाजार चौक चांदनी चौक में तेग बहादुर के सरेआम सिर काट दिहल गइल।<ref name="Grewal1998p71" /> सतीश चंद्र एह बिषद-कथावन के प्रामाणिकता पर संदेह जतवले बाड़न, जवन चमत्कार पर केंद्रित बा — औरंगजेब एहसभ में बिस्वास करे वाला ना रहलें। आगे ऊ सिख विवरण के भीतर कश्मीर में हिन्दू लोग के उत्पीड़न के कथ्य से संबंधित संदेह जतवले बाड़न, एह बात के टिप्पणी करत कि कवनो समकालीन स्रोत में ओहिजा के हिन्दू लोग के उत्पीड़न के जिक्र नइखे भइल।<ref name="sc2001">{{Cite web|url=http://www.thehindu.com/thehindu/2001/10/16/stories/05162524.htm|title=Guru Tegh Bahadur's martyrdom|last=Chandra|first=Satish|website=[[The Hindu]]|archive-url=https://web.archive.org/web/20020228135707/http://thehindu.com/thehindu/2001/10/16/stories/05162524.htm|archive-date=28 February 2002|access-date=20 October 2016}}<cite class="citation web cs1" data-ve-ignore="true" id="CITEREFChandra">Chandra, Satish. [https://web.archive.org/web/20020228135707/http://thehindu.com/thehindu/2001/10/16/stories/05162524.htm "Guru Tegh Bahadur's martyrdom"]. ''[[दि हिंदू|The Hindu]]''. Archived from [http://www.thehindu.com/thehindu/2001/10/16/stories/05162524.htm the original] on 28 February 2002<span class="reference-accessdate">. Retrieved <span class="nowrap">20 October</span> 2016</span>.</cite></ref> लुईस फेनेच प्राथमिक स्रोत सभ के कमी के आलोक में, कौनों फैसला पास करे से इनकार करे लें; हालाँकि, ऊ नोट करे लें कि एह सिख बिबरन सभ में घटना सभ में शहादत के लिखित कइल गइल रहे, एकर मकसद पाठकन में आघात से बेसी गर्व पैदा कइल रहे। आगे उनकर तर्क बा कि तेग बहादुर अपना आस्था खातिर खुद के बलिदान देले रहले; बचितर नाटक में अपना के जिक्र करत अंश में जंजू आ तिलक। <ref name=":0">{{Cite journal|last=Fenech|first=Louis E.|date=1997|title=Martyrdom and the Sikh Tradition|url=https://www.jstor.org/stable/606445|journal=Journal of the American Oriental Society|volume=117|issue=4|pages=623–642|doi=10.2307/606445|issn=0003-0279|jstor=606445|archive-url=https://web.archive.org/web/20181006000554/https://www.jstor.org/stable/606445|archive-date=6 October 2018|access-date=2 December 2017}}<cite class="citation journal cs1" data-ve-ignore="true">Fenech, Louis E. (1997). [https://www.jstor.org/stable/606445 "Martyrdom and the Sikh Tradition"]. ''Journal of the American Oriental Society''. '''117''' (4): 623–642. [[डिजिटल चीज पहिचानक|doi]]:[[doi:10.2307/606445|10.2307/606445]]. [[आइएसएसएन|ISSN]]&nbsp;[//www.worldcat.org/issn/0003-0279 0003-0279]. [[जेस्टोर|JSTOR]]&nbsp;[//www.jstor.org/stable/606445 606445]. [https://web.archive.org/web/20181006000554/https://www.jstor.org/stable/606445 Archived] from the original on 6 October 2018<span class="reference-accessdate">. Retrieved <span class="nowrap">2 December</span> 2017</span>.</cite></ref> उल्लेखनीय बा कि सिख साहित्य में एह दबंग विषय के विपरीत आधुनिकता से पहिले के कुछ सिख विवरणन में एकर दोष एगो कटु उत्तराधिकार विवाद पर डालल गइल रहे: गुरु हरकिशन के बड़ भाई राम राय के ई सुझाव दे के औरंगजेब के तेग बहादुर के खिलाफ भड़कावे के मानल गइल ऊ दरबार में चमत्कार करके आपन आध्यात्मिक महानता साबित करेला। <ref name="sc2001">{{Cite web|url=http://www.thehindu.com/thehindu/2001/10/16/stories/05162524.htm|title=Guru Tegh Bahadur's martyrdom|last=Chandra|first=Satish|website=[[The Hindu]]|archive-url=https://web.archive.org/web/20020228135707/http://thehindu.com/thehindu/2001/10/16/stories/05162524.htm|archive-date=28 February 2002|access-date=20 October 2016}}<cite class="citation web cs1" data-ve-ignore="true" id="CITEREFChandra">Chandra, Satish. [https://web.archive.org/web/20020228135707/http://thehindu.com/thehindu/2001/10/16/stories/05162524.htm "Guru Tegh Bahadur's martyrdom"]. ''[[दि हिंदू|The Hindu]]''. Archived from [http://www.thehindu.com/thehindu/2001/10/16/stories/05162524.htm the original] on 28 February 2002<span class="reference-accessdate">. Retrieved <span class="nowrap">20 October</span> 2016</span>.</cite></ref> {{Efn|Ghulam Muhiuddin Bute Shah in his ''Tarikh- i-Punjab'' reiterates this narrative.}} रंजीत सिंह के दरबारी इतिहासकार सोहन लाल सूरी अपना मजिस्ट्रेटी ''उमदत उत तवारीख'' (लगभग 1805) में हुसैन खान के तर्क के बिस्तार से दोहरावे के विकल्प चुनलें: तेग बहादुर सभ वर्ग के बिद्रोही लोग के शरण दे चुकल रहलें आ पंजाब भर में एगो बिसाल खानाबदोश सेना के कमान संभालले रहलें; एह से ओकरा के जल्दी से जल्दी नीचे गिरा दिहल गइल, कहीं ऊ निकट भविष्य में विद्रोह के घोषणा ना कर देव। <ref name="sc2001" /> == विरासत आ स्मारक == [[चित्र:Gurdwara_Rakabganj_Sahib,_Delhi.jpg|thumb|300x300px| [[गुरद्वारा रकाब गंज साहब]], दिल्ली]] गुरु हर गोविंद गुरु तेग बहादुर के बाबूजी रहले। इनके मूल नाँव ''त्याग मल'' ( {{Lang-pa|ਤਿਆਗ ਮਲ}} ) बाकिर बाद में मुगल सेना के खिलाफ लड़ाई में उनकर बहादुरी आ बहादुरी के बाद एकर नाँव ''तेग बहादुर'' रखल गइल। उहाँ के आनंदपुर साहब शहर के निर्माण कइले आ कश्मीरी पंडित लोग के एगो गुट के बचावे के जिम्मेदारी उनकरा पर रहे, जेकरा के मुगल लोग प्रताड़ित करत रहे। मुगल सम्राट औरंगजेब द्वारा तेग बहादुर के मृत्युदंड के बाद उनके आ उनके साथी लोग के याद में कई गो सिख मंदिर बनावल गइल। दिल्ली के चांदनी चौक में [[गुरुद्वारा शीशगंज साहिब|गुरद्वारा सिस गंज साहब]] जहवाँ सिर काटल गइल रहे, ओहिजा बनल। <ref name="skc">SK Chatterji (1975), Sri Guru Tegh Bahadur and the Sis Ganj Gurdwara, Sikh Review, 23(264): 100–09</ref> गुरद्वारा रकाब गंज साहब, जवन भी दिल्ली में बा, तेग बहादुर के एगो शिष्य के आवास के जगह प बनल बा, जवन कि अपना मालिक के लाश के दाह संस्कार करे खाती आपन घर जरा देले रहले। पंजाब के गुरद्वारा सिसगंज साहब ओह जगह के चिन्हित करेला जहाँ नवंबर 1675 में औरंगजेब के मुगल अधिकार के अवहेलना करत भाई जैता ( सिख संस्कार के अनुसार भाई जीवन सिंह नाम बदलल ) द्वारा ले आवल गइल शहीद गुरु तेग बहादर के सिर के इहाँ अंतिम संस्कार कइल गइल रहे। <ref>Harbans Singh (1992), "History of Gurdwara Sis Ganj Sahib", in ''Encyclopedia of Sikhism, Volume 1'', pg. 547</ref> आनंदपुर साहब के यात्रा के दौरान भाई जैता सिंह [[सोनीपत]] में दिल्ली के नजदीक एगो गांव पहुंचेले अउरी मुगल सेना भी ओ गांव पहुंच जाले। <ref>{{Cite web|url=http://dibhu.com/index.php/2019/05/06/baba-kushal-singh-ji-dahiya/|title=Archived copy|archive-url=https://web.archive.org/web/20210224171945/http://dibhu.com/index.php/2019/05/06/baba-kushal-singh-ji-dahiya/|archive-date=24 February 2021|access-date=5 January 2020}}</ref> भाई जइता गाँव के लोग से मदद के मांग करते से गाँव के लोग गुरु' सिर से भाई जैता के लुकवा दिहल। <ref>{{Cite web|url=https://hindi.pratilipi.com/read/%E0%A4%B5%E0%A5%80%E0%A4%B0-%E0%A4%AC%E0%A4%B2%E0%A4%BF%E0%A4%A6%E0%A4%BE%E0%A4%A8%E0%A5%80-%E0%A4%95%E0%A5%81%E0%A4%B6%E0%A4%BE%E0%A4%B2-%E0%A4%B8%E0%A4%BF%E0%A4%82%E0%A4%B9-%E0%A4%A6%E0%A4%B9%E0%A4%BF%E0%A4%AF%E0%A4%BE-ttraa5um49oe-4v256n71255eh9d|title=Pratilipi {{!}} Read Stories, Poems and Books|website=hindi.pratilipi.com|language=hi|archive-url=https://web.archive.org/web/20210926165700/https://hindi.pratilipi.com/read/%E0%A4%B5%E0%A5%80%E0%A4%B0-%E0%A4%AC%E0%A4%B2%E0%A4%BF%E0%A4%A6%E0%A4%BE%E0%A4%A8%E0%A5%80-%E0%A4%95%E0%A5%81%E0%A4%B6%E0%A4%BE%E0%A4%B2-%E0%A4%B8%E0%A4%BF%E0%A4%82%E0%A4%B9-%E0%A4%A6%E0%A4%B9%E0%A4%BF%E0%A4%AF%E0%A4%BE-ttraa5um49oe-4v256n71255eh9d|archive-date=26 September 2021|access-date=2020-01-05}}</ref> कुशल सिंह दहिया नाम के एगो गाँव के आदमी आगे आके गुरु के सिर के जगह आपन सिर मुगल सेना के सौंप देवे के आग्रह कइलें। <ref>{{Cite web|url=https://m.khaskhabar.com/news/news-cm-manohar-lal-unveils-statue-of-kushal-singh-dahiya-at-rai-in-sonipat-news-hindi-1-270473-KKN.html|title=कुशाल सिंह दहिया की प्रतिमा का सीएम ने किया अनावरण|date=2017-11-09|website=khas khabar|language=hi|archive-url=https://web.archive.org/web/20181127110640/https://m.khaskhabar.com/news/news-cm-manohar-lal-unveils-statue-of-kushal-singh-dahiya-at-rai-in-sonipat-news-hindi-1-270473-KKN.html|archive-date=27 November 2018|access-date=2020-01-05}}</ref> कुशल सिंह दहिया के सिर काटला के बाद गाँव के लोग सिर फेरबदल क के कुशल सिंह दहिया के सिर मुगल सेना के दे देले। <ref>{{Cite web|url=https://www.dailypioneer.com/2017/state-editions/cm-unveils-statue-of-kushal-singh-dahiya.html|title=CM unveils statue of Kushal Singh Dahiya|last=Pioneer|first=The|website=The Pioneer|language=en|archive-url=https://web.archive.org/web/20191221103657/https://www.dailypioneer.com/2017/state-editions/cm-unveils-statue-of-kushal-singh-dahiya.html|archive-date=21 December 2019|access-date=2020-01-05}}</ref> तेग बहादुर के याद कइल जाला कि ऊ धर्म के आजादी खातिर आपन जान छोड़ दिहलन, भारत के सिख आ गैर मुसलमानन के याद दिआवत बाड़न कि ऊ लोग अपना मान्यता के पालन करे आ ओकरा के पालन करे। गुरु तेग बहादुर शहीद हो गइलन, साथी भक्त भाई माटी दास, भाई सती दास आ भाई दयाला के साथे शहीद हो गइलन। <ref name="bbcgtb">{{Cite web|url=http://www.bbc.co.uk/religion/religions/sikhism/people/teghbahadur.shtml|title=Religions – Sikhism: Guru Tegh Bahadur|publisher=[[BBC]]|archive-url=https://web.archive.org/web/20170414075330/http://www.bbc.co.uk/religion/religions/sikhism/people/teghbahadur.shtml|archive-date=14 April 2017|access-date=20 October 2016}}<cite class="citation web cs1" data-ve-ignore="true">[http://www.bbc.co.uk/religion/religions/sikhism/people/teghbahadur.shtml "Religions – Sikhism: Guru Tegh Bahadur"]. [[बीबीसी|BBC]]. [https://web.archive.org/web/20170414075330/http://www.bbc.co.uk/religion/religions/sikhism/people/teghbahadur.shtml Archived] from the original on 14 April 2017<span class="reference-accessdate">. Retrieved <span class="nowrap">20 October</span> 2016</span>.</cite></ref> 24 नवंबर, इनके शहादत के तारीख भारत के कुछ हिस्सा में सार्वजनिक छुट्टी के रूप में मनावल जाला। <ref>{{Cite web|url=http://www.dnh.nic.in/Docs/14Dec2015/HolidayNotification2016.pdf|title=Letter from Administration of Dadra and Nagar Haveli, U.T.|website=Dnh.nic.in|archive-url=https://web.archive.org/web/20160806124235/http://www.dnh.nic.in/Docs/14Dec2015/HolidayNotification2016.pdf|archive-date=6 August 2016|access-date=20 October 2016}}</ref><ref>{{Cite web|url=http://arunachalipr.gov.in/GH_Restricted.htm|title=LIST OF RESTRICTED HOLIDAYS 2016|website=Arunachalipr.gov.in|archive-url=https://web.archive.org/web/20161108090922/http://arunachalipr.gov.in/GH_Restricted.htm|archive-date=8 November 2016|access-date=20 October 2016}}</ref><ref>{{Cite web|url=http://himachal.nic.in/en-IN/holidays-2016.html|title=HP Government – Holidays – Government of Himachal Pradesh, India|date=13 June 2016|website=Himachal.nic.in|archive-url=https://web.archive.org/web/20161101172904/http://himachal.nic.in/en-IN/holidays-2016.html|archive-date=1 November 2016|access-date=20 October 2016}}</ref> फांसी के सजा मुस्लिम शासन अउरी उत्पीड़न के खिलाफ सिख लोग के संकल्प कड़ा हो गइल। पशौरा सिंह के कहनाम बा कि, "अगर गुरु अर्जन के शहादत से सिख पंथ के एक संगे ले आवे में मदद मिलल रहित त गुरु तेग बहादुर के शहादत से मानव अधिकार के रक्षा के एकर सिख पहचान के केंद्रीय बनावे में मदद मिलल"। विल्फ्रेड स्मिथ <ref name="ws1981" /> के कहनाम रहल कि "नौवाँ गुरु के जबरन बाहरी, अवैयक्तिक इस्लाम में बदले के कोसिस से शहीद के नौ बरिस के बेटा गोबिंद पर साफ-साफ अमिट छाप पड़ल जे अंत में सिख समूह के संगठित क के धीरे-धीरे बाकी जानबूझ के प्रतिक्रिया दिहलें।" एगो अलग, औपचारिक, प्रतीक पैटर्न वाला समुदाय"। एहमें खालसा पहचान के सुरुआत भइल। == नोट == {{Notelist}} == संदर्भ == {{Reflist}} == बाहरी कड़ी == ; गुरु तेग बहादुर पर पियर-रिव्यू प्रकाशन * [https://web.archive.org/web/20151117023122/http://books.sikhbookclub.com/2013/pdfbook/Guru_Tegh_Bahadur_Divine_Poet_Saviour_And_Martyr_By_Ranbir_Singh.pdf गुरु तेग बहादुर] रणबीर सिंह (1975) * [https://www.jstor.org/stable/606560 सातवाँ आ नौवाँ सिख गुरु लोग के श्रेय वाली गैर-कैनोनिकल रचना], जीवन सिंह देओल, जर्नल ऑफ द अमेरिकन ओरिएंटल सोसाइटी, 121(2): 193-203, (अप्रैल। – जून, 2001) के बा। {{S-start}} {{Succession box | title = [[Sikh gurus|Sikh Guru]] | years = 20 March 1665 – 24 November 1675 | before = [[Guru Har Krishan]] | after = [[Guru Gobind Singh]] }} {{S-end}} {{Authority control}} [[श्रेणी:पंजाबी लोग]] [[श्रेणी:पन्ना जिनहन में बिना समीक्षा भइल अनुबाद बाटे]] [[श्रेणी:सिख धर्म]] bukkq8rt8mgr72d0lcyw28ml2rj9hpt कुट्टनाड 0 87746 803374 786887 2026-07-29T16:24:42Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803374 wikitext text/x-wiki [[चित्र:Kerala_Water_Transport_DS.jpg|thumb|कुट्टानाडु में सार्वजनिक परिवहन नाव सेवा]] '''कुट्टानड''' ( {{Lang-ml|കുട്ടനാട്‌}} ) भारत के [[केरल]] राज्य में अलप्पुझा, कोट्टायम आ पठानमथिट्टा जिला के कवर करे वाला इलाका हवे जे अपना बिसाल धान के खेत आ भौगोलिक बिसेसता सभ खातिर जानल जाला। ई इलाका भारत के सभसे कम ऊँचाई वाला इलाका हवे, आ दुनिया के कुछ अइसन जगहन में से एक हवे जहाँ खेती लगभग {{Convert|4|to|10|ft|m|order=flip}} समुद्र तल से नीचे होखे ला। <ref>{{Cite news|url=https://www.ndtv.com/kerala-news/coronavirus-lockdown4-kerala-government-boat-ferries-lone-passenger-sandra-babu-to-help-her-take-exam-2238752|title=Kerala Boat Ferries Lone Passenger To Help Her Take Exam|last=Press Trust of India|date=1 June 2020|work=NDTV|access-date=17 November 2020}}</ref><ref>{{Cite web|url=http://www.hindu.com/mag/2003/07/13/stories/2003071300600200.htm|title=Thirst below sea level|last=Suchitra|first=M|date=2003-08-13|website=[[The Hindu]]|archive-url=https://web.archive.org/web/20190922101804/https://www.thehindu.com/todays-paper/tp-features/tp-sundaymagazine/thirst-below-sea-level/article28523147.ece|archive-date=2019-09-22|access-date=2020-11-17}}</ref> दक्खिन भारत के प्राचीन इतिहास में कुट्टनड ऐतिहासिक रूप से महत्वपूर्ण बा आ राज्य के प्रमुख चावल उत्पादक देश हवे। कुटनाड के किसान बायोसलाइन खेती खातिर मशहूर बाड़े। खाद्य आ कृषि संगठन (एफएओ) कुट्टानड खेती प्रणाली के वैश्विक रूप से महत्वपूर्ण कृषि धरोहर प्रणाली (जीआईएएचएस) घोषित कइले बा। केरल के चार गो प्रमुख नदी पाम्बा, मीनाचिल, अचनकोविल आ मनीमाला एह इलाका में बहे लीं। ई पुन्नामाडा बैकवाटर में नाव दौड़ खातिर जानल जाला, मलयालम में वल्लमकल्ली के नाँव से जानल जाला। == इतिहास == भरपूर धान के खेत आ झील वाला एह इलाका में चावल, सब्जी & फल, मछरी, डेयरी उत्पाद आ मुर्गी के मांस के उत्पादन होला जवन स्थानीय रूप से त्रावणकोर किंगडम के तहत आलाप्पुझा, चंगनाचेरी आ कोट्टायम गाँव के बाजार में बेचल जात रहे। जब केरल राज्य के अस्तित्व आइल तब निचला कुट्टानड क्षेत्र के परिदृश्य के आधार पर आलाप्पुझा आ कोट्टायम जिला में, ऊपरी कुट्टानड के आलाप्पुझा आ पठानमथिट्टा जिला में आ उत्तरी कुट्टानड के कोट्टायम में सामिल कइल गइल। एह भूमि के पहिला दर्ज इतिहास [[संगम काल]] के साहित्य से मिलल बा। संगम युग के ग्रंथन के अनुसार '''उथियाँ चेरलटन''' ( '''पेरम चोररू उडियान चेरलथन''', अथन '''I''' या '''उडियांजेरल''' ) प्राचीन [[केरल]] में [[संगम काल]] के पहिला दर्ज चेरा राजवंश के शासक हवें। {{Sfn|Singh|2008|p=384}} इनके राजधानी कुट्टानाड (मध्य [[केरल]] ) के कुझुमूर नाँव के जगह पर रहल आ ऊ राज्य के बिस्तार अपना मूल माटी से उत्तर आ पूरब ओर कइलें। इनके जीवनकाल मोटा-मोटी ईसा पूर्व पहिली सदी से दूसरी सदी ईसवी के बीच के निर्धारित कइल गइल बा। उनकर रानी वेलियान वेनमन के बेटी वेलियान नलिनी रहली।{{प्रमाण देईं|date=February 2022}} उथियाँ चेरलथन चोल शासक करिकला चोला के समकालीन रहलें। हाथी कोर सेना आ घुड़सवार सेना खातिर उनकर तारीफ होला। वर्तमान समय के चंगनाचेरी कुट्टानड के अंत चेर राजवंश के राजा उथियान चेरलथन के राजधानी रहे। इनके वंशज सेनकुट्टुवन (चेनकुटतुवन के मतलब [[मलयालम]] में "लुभावन कुट्टुवन" ) रहल आ इनके नाँव वर्तमान समय ले चेंगनाचेरी आ चेंगना शहर सभ द्वारा ले जाइल जाला)। कुट्टूवा जनजाति के मूल स्थान कुट्टनडु के नाम से जानल जाए लागल। संगम साहित्य के मोताबिक कारीकला चोला के खिलाफ वेन्नी के लड़ाई में उथियाँ चेरलाथन हार गइल रहे आ राजधानी जरा दिहल गइल रहे।{{प्रमाण देईं|date=February 2022}} हाल के अतीत तक 'करिनिलाम'(काला धान के खेत) के नाम से कहल जाए वाला धान के खेत से करिया लकड़ी के खनन होखत रहे। कुट्टानड में अधिकतर जगह के नाँव 'कारी' (मतलब जरावल अवशेष भा कोयला) से खतम होला। कुछ परिचित जगह के नाँव बा रमणकारी, पुथुक्करी, अमीचकारी, ऊरुक्करी, मिथराकारी, मम्पुझाकरी, कैनाकारी, कंदनाकारी, थायमकारी, चठेंकरी, चथुरठियाकारी आ चेन्नमकरी ।{{प्रमाण देईं|date=February 2022}} == डिवीजन == कुट्टानाड क्षेत्र के तीन गो भाग में बाँटल गइल बा: * लोअर कुटनाड * ऊपरी कुटनाड * उत्तर कुटनाड निचला कुट्टानाडु में अम्बलपुझा, नेदुमुडी, कुट्टानाडु ( एदाथुआ, थलावाडी, किदंगरा आ मुत्तर के छोड़ के), आ आलाप्पुझा [[अलाप्पुझा जिला|जिला]] के कार्तिकपल्ली तालुका के उत्तरी आधा हिस्सा के तालुका सामिल बाड़ें। ऊपरी कुट्टानड में कार्तिकपल्ली तालुका में वीयापुरम आ पल्लीप्पड, कुट्टानड तालुक में एडथुआ, थलावाडी, किदंगरा आ मुत्तर सामिल बाड़ें; मावेलिकरा तालुका में चेन्निथला आ थ्रिप्पेरुमथुरा गाँव; आलाप्पुझा [[अलाप्पुझा जिला|जिला]] के चेंगनूर तालुका के मन्नार, कुराटिसेरी, बुधनूर, एननक्कड गाँव ; आ पठानमथिट्टा जिला के परुमाला, कदपरा, निरानम, पुलिकेझू, नेदुमपुरम, चठेंकरी, पेरिंगारा, आ कवुम्भागम गाँव में [[पथानामथिट्टा जिला|बा]]। उत्तरी कुट्टनड में वैकोम तालुका, कोट्टायम तालुका के पच्छिमी हिस्सा, आ [[कोट्टायम जिला]] के चंगनाचेरी तालुका के पच्छिमी हिस्सा सामिल बा। == बैकवाटर धान के खेती (कायल खेती) == [[चित्र:Kerala_Kuttanad2.jpg|thumb|कुट्टानड में धान के खेत]] कुट्टानाडु के प्रमुख पेशा खेती बा, जवना में धान सबसे महत्वपूर्ण कृषि उत्पाद बा। एह गतिविधि से एह इलाका के "केरल के चावल के कटोरा" के नाँव दिहल गइल हवे। [[वेम्बनाड झील]] के लगे बड़हन खेती के इलाका झील से वापस ले लिहल गइल। कुट्टानड में धान के खेती के इतिहास सदियन से पहिले के पता लगावल जा सकेला। धान के खेती के बिकास तकनीकी उन्नति आ नियामक ढाँचा में बदलाव के साथ सहसंबंधित रहल जे 19वीं आ 20वीं सदी के दौरान मौजूद रहल। पहिले के समय में मुख्य रूप से [[वेम्बनाड झील]] के उथला हिस्सा से या पम्बा नदी के परिधि से रिकलेमेशन कइल जात रहे। एह रिक्लेमेशन से धान के खेत के छोट इलाका के गठन भइल जवना के ''पदशेखराम'' कहल जाला। खेत से पानी से भराव के निकाले के काम ''चक्रम'' नाम के पानी के पहिया के इस्तेमाल से मैन्युअल रूप से कइल जात रहे। धीरे-धीरे पानी से निकाले खातिर इस्तेमाल होखे वाला मैनुअल तरीका के जगह भाप इंजन ले लिहलस।{{प्रमाण देईं|date=February 2022}} पहिले के दिनन में कुट्टानड में लूटपाट होखत रहे, जवना प त्रावणकोर महाराजा मूलम तिरुनाल के ओर से रोक लगावल गइल रहे। झील से कयाल जमीन सभ के रिक्लेमेशन में तीन गो अलग-अलग स्टेज सभ के पहिचान कइल जा सके ला। पहिला चरण के निजी उद्यमी बिना सरकार के कवनो आर्थिक सहयोग के पूरा कइले। 1865 में ट्रेवेनकोर किंगडम द्वारा कइल गइल पैटम घोषणा, 1865 से 1888 के बीच रिकलेमेसन गतिविधि सभ के बहुत ढेर बढ़ावा दिहलस। एह दौरान पोल्डर सभ के पानी मुक्त करे के काम मैन्युअल रूप से ''चक्रम'' के इस्तेमाल से कइल गइल, जेकरा से बड़ पैमाना पर रिकलेमेशन ना कइल जा पावत रहल। एह दौरान मात्र लगभग 250 हेक्टेयर जमीन के पुनर्प्राप्ति भइल। एह दौरान वेनाडु कयाल आ मदथिल कायल के रिकलेम कइल गइल आ वेम्बनाड झील से रिक्लेम कइल गइल पहिला "कायल नीलाम" मानल जाला। <ref>{{Cite news|url=http://www.fao.org/fileadmin/templates/giahs_assets/Information_Resources_Annexes/Japan_Forum/Kuttanad_Below_Sea_Level_Farming_System.pdf|title=The Kuttanadu Below Sea Level Farming System, India|last=M.S. Swaminathan, FRS|date=May 2013|work=FAO Website|access-date=8 Oct 2013|archive-url=https://web.archive.org/web/20150611175117/http://www.fao.org/fileadmin/templates/giahs_assets/Information_Resources_Annexes/Japan_Forum/Kuttanad_Below_Sea_Level_Farming_System.pdf|archive-date=11 June 2015|pages=8}}</ref> कयाल खेती में ई अग्रणी रिक्लेमेशन गतिविधि कुट्टानाडु के कैनाडी गाँव के दुनु भाई मथाई लुका पलिथानम आ औसेफ लुका पलिथानम कइले रहले।<ref>{{Cite news|url=http://www.kerala.gov.in/docs/publication/2013/kc/september_13/16.pdf|title=Kuttanad; Our Heritage Our wealth|last=G S Unnikrishanan Nair|date=Sep 2013|work=KERALA CALLING|access-date=26 Sep 2013|archive-url=https://web.archive.org/web/20130927092458/http://www.kerala.gov.in/docs/publication/2013/kc/september_13/16.pdf|archive-date=2013-09-27|pages=16–20}}</ref> दूसरा चरण 1888 के दौरान शुरू भइल। एह दौरान एगो रिक्लेमेशन चलायल एरावी केसवा पनिकर कइले रहले। उ चेन्नमकरी नदी के मुहाना से वेम्बनाड कयाल के रिक्लेम करे के फैसला कइलें, काहेंकी इ पीछे के पानी से जुड़ जाले। पुनर्प्राप्त कयाल के ' अतुमुटु कायल' के नाम से जानल जात रहे। एही साल एगो अउरी बड़हन रिक्लेमेशन रहे सेमिनरी कायल जवन कोट्टायम रूढ़िवादी सेमिनरी कइले रहे।{{प्रमाण देईं|date=February 2022}} पानी के निकासी खातिर मिट्टी के तेल के इंजन के शुरूआत के परिणामस्वरूप झील के बिसाल इलाका सभ के खेती खातिर रिक्लेमेशन भइल। एह से किसान लोग झील के गहिराह हिस्सा में उतरे पर विचार कइ सकल। 1898 से 1903 के बीच के समय में, रिक्लेमेशन गतिविधि के नेतृत्व पल्लीथनम लुका मथाई (उर्फ पल्लीथानाथु मथैचेन) कइलें जे चेरुकारा कायल आ पल्लीथनम मूवायराम कायल के रिक्लेम कइलें। बाकिर दुसरका चरण (1890 से 1903) के रिक्लेमेशन गतिविधि ठप्प हो गइल काहे कि 1903 में मद्रास सरकार द्वारा कयाल रिकलेमेशन पर रोक लगावल गइल रहे। एह दौरान चेरुकाली कायल, राजापुरम कायल, आरुपंकू कायल, पंथर्दू पंकू कायल आ माथी कायल अउरी प्रमुख रिक्लेमेशन रहलें।{{प्रमाण देईं|date=February 2022}} 1912 में मद्रास सरकार त्रावणकोर सरकार के ओर से तीन चरण में अउरी रिक्लेमेशन खातिर एगो प्रस्ताव के मंजूरी दे दिहलस। एह योजना के तहत कयल जमीन के ब्लॉक में पुनर्प्राप्ति खातिर अधिसूचित कइल गइल रहे जवना के नाम हर ब्लॉक के अंग्रेजी वर्णमाला के अक्षर से रखल गइल रहे| कुल 19,500 एकड़ कयाल जमीन में से 12,000 एकड़ जमीन के 1913 से 1920 के बीच रिकलेम कइल गइल। 1913 में प्रतिबंध हटावे के बाद पल्लीथनम लुका मथाई कुट्टानाडु के कुछ अउरी प्रमुख परिवारन के साथे मिल के कुल क्षेत्रफल 2,400 एकड़ के ई-ब्लॉक कायल के रिक्लेम कइल गइल। ई कुट्टानाडु के सबसे बड़ कायल निलाम ह। एह उद्यम में सीजे कुरियन, एक्स एमएलसी आ श्री जॉन इल्लिकालम उनुकर मुख्य साझेदार रहलें। 1914 से 1920 के बीच के रिक्लेमेशन, जेकरा के नया रिक्लेमेशन के नाँव से जानल जाला, तीन काल में भइल। पहिला अवधि में 6300 एकड़ के ब्लॉक ए से जी के रिकलेम कइल गइल। सी ब्लॉक, डी ब्लॉक (अत्तुमुखम आरायीराम (अतुमुतु कयाल), थेक्के आरायीराम आ वडक्के आरयिम) आ ई ब्लॉक (एरुपतिनालायिराम कायल) एफ ब्लॉक (जज के आरयिराम कायल) आ जी ब्लॉक (कोचु कायल) एह दौरान पुनर्प्राप्त प्रमुख कायल नीलम हवें।{{प्रमाण देईं|date=February 2022}} नया रिकलेमेशन के दूसरा दौर में 3600 एकड़ के क्षेत्रफल में फैलल ब्लॉक एच से एन के पल्लीथनम लुका मथाई, कुन्नुम्पुराथु कुरिएन, वाचपरम्पिल माथेन, पझायापरम्पिल चको, कुन्नाथुसेरिल पेयस, एट्टूपरैल जेवियर आ पट्टासेरी पीपी मथाई के नेतृत्व में रिकलेम कइल गइल। नया रिकलेमेशन के तीसरा अवधि के दौरान श्री मूलम लोकप्रिय सभा के तत्कालीन सदस्य पल्लीथनम लुका मथाई के नेतृत्व में आठ परिवार के संयुक्त प्रयास से 1,400 एकड़ में फैलल आर ब्लॉक कायल के रिक्लेम कइल गइल। रिक्लेमेशन में उनकर साझीदारन में वाचपरम्पिल माथेन, पझायापरम्पिल चक्को, एट्टूपरायल जेवियर, पट्टासेरी पीपी मथाई, करिककुझी पोन्नाडा वक्कल मथुल्ला मप्पिला (ई एंड एफ ब्लॉक), मेलेडोम, परुथिकल आ कंडाकुडी शामिल रहले. एक बेर त्रावणकोर के राजा एरुपथिनालायिराम कायल के दौरा कइलन त ऊ खुश हो गइलन आ कारिक्कुझी मथुल्ला मप्पिला से कहले कि उनका सम्मान खातिर पोन्नाडा खरीदे के चाहत रहे। चूँकि ऊ पोन्नाडा ना खरीदले रहलें एहसे ऊ "पोन्नाडा वक्कल" (शब्द से पोन्नाडा) पेश कइलन। एकरा बाद उनकर घर पोन्नाडवाक्कल के नाम से जानल गइल।{{प्रमाण देईं|date=February 2022}} 1920 से 1940 तक चावल के दाम में भारी गिरावट के चलते रिक्लेमेशन के गतिविधि ठप हो गइल। [[चित्र:Kuttanad_beauty.jpg|alt=Kuttanad beauty|thumb| '''कुट्टानड के सुंदरता''']] [[चित्र:Rice_fields_of_Kuttanad.jpg|left|thumb| धान के खेत में पारंपरिक पानी के टरबाइन का उपयोग कर रहल किसान]] 1920 से 1940 के बीच चावल के दाम में भारी गिरावट के चलते रिकलेमेसन गतिविधि में धीमापन आइल, लेकिन 1940 के दशक के शुरुआत में एकरा के फेर से गति मिलल। एह दौरान कृषि उत्पादन बढ़ावे खातिर एगो सरकार "अधिक खाद्य उगाईं" अभियान शुरू कइलस आ प्रोत्साहन के प्रावधान से नया पुनर्वास के प्रोत्साहित कइल गइल। इलेक्ट्रिक मोटर के आगमन से पहिले के समय के तुलना में रिकलेमेशन अपेक्षाकृत आसान, सस्ता आ कम जोखिम वाला हो गइल। रिक्लेमेशन के अंतिम खंड यानी क्यू, एस आ टी ब्लॉक एह दौरान जोसेफ मुरिकन (मुरिकेन आउटहचन) आ उनकर मेहरारू के परिवार पुथनप्पुरा पंचरा (वेलियानाडु) द्वारा बनावल गइल। जइसे-जइसे ए इलाका में खेती बढ़ल, किसान धान के खेती खाती साल में होखेवाला दु चक्र से अपना के बाध्य महसूस कइले। जवना के कारण कुट्टानाडु में पीये लायक पानी के सीमित उपलब्धता बा। मानसून के मौसम में पहाड़ से पानी नदी से होके समुंद्र में बह के कुट्टानाडु में पीये लायक पानी ले आवेला। बाकिर गर्मी के समय एह इलाका के कम स्तर के कारण समुंद्र के पानी कुट्टानाडु में प्रवेश करे ला जेवना से पानी के लवणता काफी बढ़ जाला आ ई पीये लायक ना रह जाला।{{प्रमाण देईं|date=February 2022}} साथ ही कुट्टानाड के एफएओ द्वारा ग्लोबलली इम्पोर्टेंट एग्रीकल्चरल हेरिटेज सिस्टम (GIAHS) के रूप में मान्यता दिहल गइल बा। == थोट्टप्पली स्पिलवे == थोट्टप्पली स्पिलवे परियोजना के डिजाइन कुट्टानड में बाढ़ के स्थिति के स्थायी समाधान के रूप में बनावल गइल रहे। एह कार्यक्रम के परिकल्पना अइसन कइल गइल कि पम्बा, मणिमालायर आ अचनकोविल से बाढ़ के पानी वेम्बनाद झील में चहुँपे से पहिले समुद्र में मोड़ दिहल गइल। ई स्पिलवे 1955 में चालू भइल <ref>{{Cite news|url=https://www.onmanorama.com/news/kerala/2020/08/01/thottappally-spillway-opened-to-prevent-flooding-kuttanad.html|title=Thottappally spillway opened to prevent flooding in Kuttanad|work=OnManorama}}</ref> 1968 में भारत सरकार प्रस्ताव रखलस कि नदी के ओह पार एगो बंधा बनावल जाय ताकि गर्मी के समय में समुंद्र के पानी के कुट्टानड के भीतर ना आवे दिहल जा सके, जेह से किसान लोग के हर साल एक ठो अतिरिक्त फसल के खेती हो सके। एह परियोजना के योजना तीन चरण में बनावल गइल रहे, दक्षिण ओर, उत्तर ओर आ एगो अउरी चरण में दुनो खंड के जोड़े खातिर। परियोजना में देरी हो गइल अउरी जब तक पहिला दु चरण पूरा हो गइल तब तक ए परियोजना खाती आवंटित पूरा पइसा खतम हो गइल अउरी अंतिम चरण अनिश्चितता में लटक गइल। जवन किसान परियोजना पूरा होखला के बाद बहुत आर्थिक लाभ के उम्मीद करत रहले, उ लोग मामला के अपना हाथ में लेवे के फैसला कइलें अउरी 1972 में एक रात किसान के एगो बड़ समूह उत्तर अउरी दक्षिण ओर के बीच के खाई के माटी से भर देलस। आजु ले बंधा के दुनों खंड के बीच के माटी के तटबंध बनल बा। एकरे साथ दिसंबर–जून के दौरान जब खारा पानी घुसे ला तब शटर सभ के रेगुलेटर बंद कइल संभव हो गइल, आ फिर मानसून के दौरान खोलल जा सके ला। एक बेर ठनीरमुक्कम बंड आ स्पिलवे संचालित हो गइल त कुट्टानड में दू गो फसल संभव हो गइल। भले ही बंड से किसान के जीवन के गुणवत्ता में सुधार भइल बा, लेकिन ए बंड के चलते पर्यावरण के गंभीर समस्या पैदा होखे के आरोप बा। बैकवाटर जवन मछरी के भरमार रहे आ एह इलाका के लोग के मुख्य भोजन के हिस्सा रहे ओकरा प्रजनन खातिर थोड़ बहुत नमकीन पानी के जरूरत होला। एह बंधा के चलते एह इलाका में मछरी के किसिम खराब हो गइल बा आ मछुआरा लोग 2005 ले एह बंधा के विरोध कइले बा। बंधा से समुंद्र के बैकवाटर के साथे सामंजस्य भी बाधित हो गइल बा आ पानी के खरपतवार के सर्वव्यापी होखे जइसन समस्या पैदा भइल बा जवन बंधा से पहिले ना देखल गइल रहे। बंधा के निर्माण से पहिले खारा पानी में बैकवाटर के साफ करे के प्रवृत्ति रहे, लेकिन अब अइसन ना होखेला, जवना के चलते बैकवाटर अउरी पास के जमीन के प्रदूषण होखेला। == उल्लेखनीय मूल निवासी आ निवासी लोग == {{Columns-list|<!---♦♦♦♦ Do not add anyone to this list unless they have their own Wikipedia article ♦♦♦♦---> * [[Kavalam Sreekumar]] - Singer * [[Pallithanam Luca Matthai]], former member of Sree Moolam Popular Assembly and pioneer of kayal cultivation in Kuttanad. * [[Renji Panicker]], actor, director, writer and a journalist from Nedumudi village of Kuttanadu. * [[K. K. Nayar]], administrator and politician. * [[Nedumudi Venu]], Actor * [[Ayyappa Panikkar|K. Ayyappa Panikkar]] * [[Kavalam Narayana Panikkar]] * [[Thakazhi Sivasankara Pillai]], famous novelist, [[Jnanpith]] scholar * [[Vinayan]], film director. * [[K. M. Panikkar]], scholar, journalist, historian, administrator, diplomat * [[K P Sasidharan]] Professor, Malayalam writer, Soviet Land, Sangeeth Nataka academy award winner * [[Baselios Geevarghese II]], Kallasseri Bava, Kurichy, 3rd Catholicos of Orthodox Church * [[Kuriakose Elias Chavara]], Indian saint * [[John Abraham (director)|John Abraham]], film director * [[Kunchacko Boban]], film actor and director * [[Saji Thomas]], International Sportsman, Arjuna Awardee * [[I. C. Chacko, Illiparambil|I C Chacko]], physicist and geologist, state geologist of Travancore State from 1906 to 1921 * [[Thomas Chandy]], MLA, politician * [[Ramesh Chennithala]], former Minister, UDF political leader * [[Guru Gopinath]], Indian classical dancer * [[K. C. Joseph (Kuttanadu politician)|K. C. Joseph]], former MLA, * [[Kunchacko]], film producer and director * [[Mar Thomas Kurialacherry]], former archbishop * [[Oommen Mathew]], Former MLA, politician * [[Venu Nagavally]], film actor and director * [[Mankompu Sivasankara Pillai]] - [[Kathakali]] artist of the classical dance-drama's southern style * [[Ponkunnam Varkey]], novelist * [[Sebastian Xavier]], former Indian Olympic swimmer from Kerala|colwidth=22em}} == राजनीति == कुट्टानड विधानसभा क्षेत्र आलाप्पुझा के हिस्सा रहे। साल 2008 में [[लोकसभा]] परिसीमन के बाद अब इ मावेलिकारा क्षेत्र के बा। <ref>{{Cite web|url=http://archive.eci.gov.in/se2001/background/S11/KL_Dist_PC_AC.pdf|title=Assembly Constituencies&nbsp;— Corresponding Districts and Parliamentary Constituencies|website=Kerala|publisher=Election Commission of India|archive-url=https://web.archive.org/web/20090304011026/http://archive.eci.gov.in/se2001/background/S11/KL_Dist_PC_AC.pdf|archive-date=2009-03-04|access-date=2008-10-20}}</ref> == इहो देखल जाव == * [[अलाप्पुझा|अलप्पुझा]] == संदर्भ == {{Reflist}} == आगे पढ़ल जा सकेला == * ''[http://www.countercurrents.org/en-suchitra150703.htm समुद्र तल से नीचे प्यास] {{Webarchive|url=https://web.archive.org/web/20050217031926/http://countercurrents.org/en-suchitra150703.htm |date=2005-02-17 }}'', एम सुचित्र, ''[[दि हिंदू|द हिंदू]]'', 15 जुलाई 2003 * केटी राममोहन के ह। ''चावल के किस्से: कुट्टानड, दक्षिण पश्चिम भारत'' के बा। विकास अध्ययन केंद्र, तिरुवनंतपुरम, 2006 में प्रकाशित भइल।{{ISBN|81-86353-03-8}}[[ISBN (identifier)|आईएसबीएन के बा]]&nbsp;[[Special:BookSources/81-86353-03-8|81-86353-03-8 पर संपर्क कइल जा सकेला]] * केटी राममोहन. 2008. हेलेन सेलिन (संपादक) में 'दक्षिण भारत में धान के खेत के पुनर्प्राप्ति'। ''गैर-पश्चिमी संस्कृतियन में विज्ञान, प्रौद्योगिकी, आ चिकित्सा के इतिहास के विश्वकोश।'' खंड 1. 2. । बर्लिन, हाइडलबर्ग आ न्यूयॉर्क: स्प्रिंगर-वेर्लाग।{{ISBN|978-1-4020-4559-2}}[[ISBN (identifier)|आईएसबीएन के बा]]&nbsp;[[Special:BookSources/978-1-4020-4559-2|978-1-4020-4559-2 पर संपर्क कइल जा सकेला]] == बाहरी कड़ी == * [https://www.keralatourism.org/hindi/destination/kuttanad-alappuzha/59 कुट्टनाड - केरल के चावल का कटोरा], केरल टूरिज्म के वेबसाइट पर (हिंदी में)। [[श्रेणी:Harv and Sfn no-target errors]] [[श्रेणी:जवन निर्देशांक विकिडेटा पर मौजूद बाटे]] [[श्रेणी:केरल के भूगोल]] [[श्रेणी:पन्ना जिनहन में बिना समीक्षा भइल अनुबाद बाटे]] 5xt0d1qwtf7j7g8q70frnx0wze3g9ef द्वारिकाधीश मंदिर 0 88193 803363 789040 2026-07-29T16:23:48Z SM7Bot 7808 /* ढांचा */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803363 wikitext text/x-wiki {{Infobox Hindu temple|name=Dwarkadheesh Temple <br/> द्वारकाधीश मंदिर|image=Dwarkadheesh temple.jpg|alt=|caption=The temple sikhars with the entrance in front|map_type=India Gujarat|map_caption=Location in Gujarat|coordinates={{coord|22|14|16.39|N|68|58|3.22|E|type:landmark_region:IN|display=inline,title}}|country=[[India]]|state=[[Gujarat]]|locale=[[Dwarka]]|elevation_m=|website={{URL|https://www.dwarkadhish.org/}}}} {{Char Dham}} '''द्वारकाधीश मंदिर''', जेकरा के '''जगत मंदिर''' भी कहल जाला, [[कृष्ण]] के समर्पित एगो [[हिंदू मंदिर]] हवे, जिनके इहाँ ''द्वारकाधीश'', या 'द्वारका के राजा' नाँव से पूजल जाला। ई मंदिर [[गुजरात|भारत के गुजरात]] के द्वारका शहर में स्थित बा, जवन हिंदू तीर्थ परिक्रमा [[चारो धाम|चार धाम]] के जाए वाला जगहन में से एगो ह। 72 खंभा पर बनल एह पांच मंजिला भवन के मुख्य तीर्थ जगत मंदिर भा निज मंदिर के नाम से जानल जाला। पुरातात्विक खोज से पता चलेला कि मूल मंदिर के निर्माण सबसे पहिले 200 ईसा पूर्व में भइल रहे। 15वीं-16वीं सदी में एह मंदिर के दोबारा निर्माण आ बिस्तार कइल गइल।<ref>1988, P. N. Chopra, "Encyclopaedia of India, Volume 1", page.114</ref> परंपरा के अनुसार मूल मंदिर के निर्माण कृष्ण के पोता वज्रनाभ द्वारा हरि-गृह (कृष्ण के आवासीय स्थान) के ऊपर बनवावल मानल जाला। मूल संरचना के महमूद बेगडा द्वारा 1472 में नष्ट कर दिहल गइल, आ एकरे बाद 15वीं-16वीं सदी में मारु-गुर्जर शैली में बनवावल गइल। ई मंदिर भारत में [[हिंदू|हिन्दू लोग]] द्वारा पवित्र मानल जाए वाला ''चार धाम'' तीर्थ यात्रा के हिस्सा बन गइल। [[आदि शंकर|आदि शंकराचार्य]], 8वीं सदी के हिन्दू धर्मशास्त्री आ दार्शनिक, एह तीर्थ के दौरा कइले। बाकी तीन में रामेश्वरम, बद्रीनाथ अउरी [[पुरी]] शामिल बा। आजुवो मंदिर के भीतर एगो स्मारक उनुका यात्रा के समर्पित बा। द्वारकाधीश उपमहादीप के [[विष्णु]] के 98वां दिव्य देसम हवे, जेकर महिमा ''दिव्य प्रबंध'' पवित्र ग्रंथन में बा।{{Sfn|Bandyopadhyay|2014|p=71}} मंदिर औसत समुद्र तल से ऊपर {{Convert|12.19|m}} के ऊँचाई पर बा। एकर मुँह पच्छिम के ओर बा। मंदिर के लेआउट में एगो ''गर्भगृह ( निजमंदिर'' या ''हरिग्रह'' ) आ एगो अंतराल (एगो एंटीचेम्बर) बाटे।{{Sfn|Paramāra|1996|p=87}} हालाँकि, मौजूदा मंदिर के तारीख 16वीं सदी के बतावल जाला। == दंतकथा == हिन्दू किंवदंती के अनुसार द्वारका के निर्माण एगो जमीन के अइसन टुकड़ा पर कृष्ण द्वारा कइल गइल रहे जेकरा के समुद्र से वापस हासिल कइल गइल रहे। दुर्वास ऋषि एक बेर कृष्ण आ उनकर पत्नी रुक्मिणी के घरे गइल रहले। ऋषि के इच्छा रहे कि ई जोड़ी उनका के अपना महल में ले जास। ई जोड़ी सहजता से मान गइल आ ऋषि के साथे अपना महल के ओर चलल शुरू कर दिहलस। कुछ दूरी के बाद रुक्मिणी थक गइली त उ कृष्ण से पानी के निहोरा कइली। कृष्ण एगो पौराणिक छेद खोदले जवन [[गंगा नदी|गंगा]] नदी के ओह जगह पर ले आइल। दुर्वास ऋषि खिसिया गइलन आ रुक्मिणी के ओह जगह पर रहे के सराप दिहलन। जवना मंदिर में रुक्मिणी के तीर्थ मिलेला, उहे मंदिर मानल जाता जहां उ ओह समय खड़ा रहली। == इतिहास == [[चित्र:Dwarakadheesh_temple,_Dwaraka.jpg|left|thumb|200x200px| मंदिर के मुख्य प्रवेश द्वार तक जाए वाली सीढ़ी]] [[गुजरात]] के द्वारका शहर के इतिहास सदियन से चलल आ रहल बा, आ [[महाभारत]] महाकाव्य में एकर जिकिर द्वारका राज्य के रूप में कइल गइल बा। गोमती नदी के तीरे स्थित एह शहर के किंवदंती में [[कृष्ण]] के राजधानी के रूप में बतावल गइल बा। लिखाई वाला पत्थर के ब्लॉक, पत्थर सभ के सजावट के तरीका से पता चले ला कि डवेल के इस्तेमाल भइल बा आ एह जगह पर मिलल लंगर सभ के जांच से पता चले ला कि बंदरगाह के जगह खाली ऐतिहासिक समय के हवे, पानी के नीचे के कुछ ढाँचा के समय देर मध्यकालीन समय के बा। तटीय कटाव शायद ओह चीज के नाश के कारण रहे जवन एगो प्राचीन बंदरगाह रहे। <ref>{{Cite journal|last=Gaur|first=A.S.|last2=Sundaresh and Sila Tripati|date=2004|title=An ancient harbour at Dwarka: Study based on the recent underwater explorations|journal=Current Science|volume=86|issue=9}}</ref> हिन्दू लोग के मानना बा कि मूल मंदिर के निर्माण कृष्ण के परपोता वज्रनाभ द्वारा कृष्ण के आवासीय महल के ऊपर कइल गइल। एकरा के सुल्तान महमूद बेगड़ा द्वारा 1472 में नष्ट कर दिहल गइल। चौलुक्य शैली में वर्तमान मंदिर के निर्माण 15-16वीं सदी में भइल रहे। मंदिर के क्षेत्रफल 27-मीटर बाई 21-मीटर बा आ पूरब-पच्छिम लंबाई 29-मीटर आ उत्तर-दक्खिन चौड़ाई 23 मीटर बा। मंदिर के सबसे ऊँच चोटी 51.8 मीटर ऊँच बा। == धार्मिक महत्व == [[चित्र:Dwarkadhish_Temple_20.jpg|thumb|गोमती नदी के पास द्वारकाधीश मंदिर, द्वारका]] चूँकि ई स्थल प्राचीन नगरी द्वारका आ [[महाभारत]] युग के कृष्ण से जुड़ल बा एहसे [[हिंदू|हिन्दू लोग]] खातिर ई एगो महत्वपूर्ण तीर्थस्थल बा। ई ''"कृष्ण"'' सर्किट से संबंधित 3 गो मुख्य तीर्थस्थल में से एगो हवे, जवना में [[हरियाणा]] राज्य ''के कुरुक्षेत्र के 48 कोस परिक्रमा'', [[उत्तर प्रदेश]] राज्य के [[मथुरा]] में ब्रज परिकर्मा आ [[गुजरात]] राज्य के द्वारकाधीश मंदिर में ''द्वारका परिक्रमा'' (द्वारकादीश यात्रा) बा। मंदिर के ऊपर के झंडा पर सुरुज आ चंद्रमा के देखावल गइल बा जेवना से ई बतावल जाला कि जबले सूरज आ चंद्रमा के पृथिवी पर मौजूद ना होखे तबले कृष्ण उहाँ मौजूद रहीहें। <ref name="indianmirror1">{{Cite web|url=http://www.indianmirror.com/temples/dwarkadish-temple.html|title=Dwarkadish Temple, Dwarkadish Temple Dwarka, Dwarkadish Temple in India|publisher=Indianmirror.com|access-date=4 March 2014}}</ref> दिन में पांच बेर तक झंडा बदलल जाला, लेकिन चिन्ह उहे रहेला। मंदिर के पांच मंजिला संरचना बा जवन बहत्तर खंभा प बनल बा। मंदिर के शिखर 78.3 मीटर ऊँच बा। <ref name="indianmirror1" /> मंदिर चूना पत्थर से बनल बा जवन अभी भी नीमन हालत में बा। मंदिर में जटिल मूर्तिकला के डिटेल बनावल गइल बा। मंदिर में दू गो प्रवेश द्वार बा। मुख्य प्रवेश द्वार (उत्तर प्रवेश द्वार) के "मोक्ष द्वार" (मोक्ष के द्वार) कहल जाला। एह प्रवेश द्वार से मुख्य बाजार में पहुँचल जाला। दक्खिन के प्रवेश द्वार के "स्वर्ग द्वार" (स्वर्ग के द्वार) कहल जाला। एह दुआर के बाहर 56 सीढ़ी बा जवन गोमती नदी के ओर जाला। <ref>[[Dwarkadhish Temple#Chakravarti|Chakravarti 1994]], p. 140</ref> हालांकि एकर उत्पत्ति के बारे में साफ-साफ जानकारी नइखे, लेकिन पूरा भारत में हिंदू मठ संस्थान के निर्माण करे वाला संकराचार्य द्वारा स्थापित हिंदू धर्म के अद्वैत स्कूल चार धाम के उत्पत्ति के श्रेय द्रष्टा के बतावेला। <ref name="Mittal" /> चारो मठ भारत के चारो कोना में बा आ इनहन के अनुचर मंदिर उत्तर में बद्रीनाथ में बद्रीनाथ मंदिर, पूरब में पुरी में [[जगन्नाथ मंदिर, पुरी|जगन्नाथ मंदिर]], पश्चिम में द्वारका में द्वारकाधीश मंदिर आ दक्खिन में रामेश्वरम में रामनाथस्वामी मंदिर बाड़ें। भले वैचारिक रूप से मंदिर सभ हिंदू धर्म के संप्रदाय, यानी [[शैव मत|शैव]] आ वैष्णव धर्म के बीच बँटल बाड़ें, चार धाम के तीर्थयात्रा एगो अखिल हिन्दू मामला हवे। <ref name="Brockman">[[Dwarkadhish Temple#Brockman|Brockman 2011]], pp. 94-96</ref> हिमालय में चार गो निवासस्थान बाड़ें जिनहन के छोट चार धाम ( ''छोटा'' मतलब छोट) कहल जाला: बद्रीनाथ, केदारनाथ, [[गंगोत्री]] आ यमुनोत्री - ई सभ हिमालय के तलहटी पहाड़ी पर पड़े लें। मूल चार धाम सभ के बिभेद करे खातिर 20वीं सदी के बीच में ''छोटा'' नाँव जोड़ल गइल। आधुनिक काल में जइसे-जइसे एह जगहन पर तीर्थयात्री लोग के संख्या बढ़ल, एकरा के हिमालयन चार धाम कहल जाला। भारत के चार गो कार्डिनल प्वाइंट के पार के यात्रा के हिन्दू लोग पवित्र मानेला जे लोग अपना जीवन में एक बेर एह मंदिरन के दौरा करे के चाहत राखेला।<ref name="Gwynne">[[Dwarkadhish Temple#Gwynne|Gwynne 2008]], Section on Char Dham</ref> परंपरागत रूप से यात्रा के सुरुआत पुरी से पूरबी छोर से होला, घड़ी के दिशा में अइसन तरीका से आगे बढ़े ला जेकर पालन आमतौर पर हिंदू मंदिर सभ में परिक्रमा खातिर कइल जाला।<ref name="Gwynne" /> मंदिर 6.00 बजे से खुलल बा&nbsp;बजे से 1.00 बजे तक के बा&nbsp;शाम आ 5.00 बजे के बा&nbsp;रात के साढ़े नौ बजे तक&nbsp;साँझ के बा. [[जन्माष्टिमी|कृष्ण जन्माष्टमी]] पर्व भा गोकुलाष्टमी, [[कृष्ण]] के जनमदिन के मनावे के सुरुआत वल्लभ (1473-1531) कइले रहलें। एगो किंवदंती के अनुसार मीरा बाई, प्रसिद्ध राजपूत राजकुमारी जे कवि-संत आ कृष्ण के कट्टर भक्त भी रहली, एह मंदिर में देवता में विलय हो गइली। {{Sfn|Desai|2007|p=285}} ई भारत के सात गो पवित्र शहर सप्त पुरी में से एगो हवे। {{Sfn|Bansal|2008|p=20-23}} ई मंदिर द्वारका पीठ के भी स्थान हवे जे [[आदि शंकर|आदि शंकराचार्य]] (686-717) द्वारा स्थापित चार गो पीठ (धार्मिक केंद्र) सभ में से एक हवे जे देश में [[हिंदू]] धार्मिक बिस्वास सभ के एकीकरण के सुरुआत कइलें। ई चार मंजिला संरचना ह जवन देश के अलग-अलग हिस्सा में शंकराचार्य द्वारा स्थापित चार पीठ के प्रतिनिधित्व करेला। इहाँ के देवालन पर शंकराचार्य के जीवन इतिहास के चित्रण करे वाला चित्र बा जबकि गुंबद में अलग-अलग मुद्रा में [[शिव]] के नक्काशी बा। {{Sfn|Desai|2007|p=285}} {{Sfn|Bandyopadhyay|2014|p=71}} == ढांचा == [[चित्र:Dwarakadheesh Temple, 2014.jpg|thumb|पांच मंजिल के नजारा]] ई 72 खंभा पर बनल पाँच मंजिला इमारत हवे (60 खंभा वाला बलुआ पत्थर के मंदिर के भी जिकिर कइल गइल बा)। <ref name="Brit">{{Cite web|url=http://www.britannica.com/EBchecked/topic/174909/Dwarka|title=Dwarka|publisher=Encyclopædia Britannica|access-date=19 April 2015}}</ref> {{Sfn|Desai|2007|p=285}} {{Sfn|Bansal|2008|p=20-23}} मंदिर में दू गो महत्वपूर्ण प्रवेश द्वार बाड़ें, एगो मुख्य प्रवेश द्वार हवे जेकरा के ''मोक्ष द्वार'' (मतलब "मुक्ति के दरवाजा") कहल जाला आ बाहर निकले के दरवाजा जेकरा के ''स्वर्ग द्वार'' ( मतलब: "स्वर्ग के फाटक")। {{Sfn|Bansal|2008|p=20-23}} गर्भगृह में देवता बनावल मुख्य देवता द्वारकादीश हवें जेकरा के विष्णु के त्रिविक्रम रूप में जानल जाला आ चार गो भुजा के साथ चित्रित कइल गइल बा। {{Sfn|Bansal|2008|p=20-23}} मुख्य वेदी के बाईं ओर के कक्ष पर कृष्ण के बड़ भाई बलराम बाड़ें। दाहिने ओर के कक्ष में कृष्ण के बेटा आ पोता प्रद्युम्न आ अनिरुद्ध के मूर्ति बाड़ी सऽ। केंद्रीय तीर्थ के आसपास कई गो तीर्थ में देवी [[राधा]], जाम्बवती, सत्यभामा आ [[लक्ष्मी]] के मूर्ति बाड़ी स {{Sfn|Bansal|2008|p=20-23}} माधव रावजी (कृष्ण के दूसर नाँव), बलराम आ ऋषि दुर्वासा के तीर्थ भी एह मंदिर में मौजूद बा। {{Sfn|Bandyopadhyay|2014|p=71}} द्वारकाधीश के केंद्रीय तीर्थ के ठीक सामने राधा कृष्ण आ देवकी के समर्पित दू गो अलग-अलग तीर्थ भी बाड़ें [[चित्र:Dwarakadheesh_temple_in_Dwarka_-_Gujarat,_India_(5933598087).jpg|thumb|प्लेटफार्म के चारों ओर नक्काशी]] मंदिर के शिखर {{Convert|78|m}} के ऊँचाई तक जाला आ बहुत बड़हन झंडा फहरावल जाला जवना पर [[सुरुज|सूर्य]] आ [[चंद्रमा]] के चिन्ह लगावल गइल बा। {{Sfn|Bansal|2008|p=20-23}} त्रिकोणीय आकार के ई झंडा {{Convert|50|ft}} होला लंबाई के बा। एह झंडा के दिन में चार बेर नया झंडा से बदलल जाला आ हिन्दू लोग नया झंडा खरीद के एकरा के फहरावे खातिर भारी रकम देला एह खाता से मिले वाला पईसा मंदिर के ट्रस्ट फंड में जमा क के मंदिर के संचालन अउरी रखरखाव के खर्चा पूरा कइल जाला। {{Sfn|Bandyopadhyay|2014|p=71}} == अवार्ड == द्वारकाधीश जगत मंदिर के 22 मार्च 2021 के ''वर्ल्ड टैलेंट ऑर्गेनाइजेशन'', न्यू जर्सी, अमेरिका द्वारा "वर्ल्ड अमेजिंग प्लेस" के प्रमाणपत्र से सम्मानित कइल गइल। <ref>{{Cite news|url=https://tv9gujarati.com/gujarat/devbhumi-dwarka/devbhumi-dwarka-dwarkas-jagatmandir-gets-world-amazing-palace-honor-253162.html|title=Devbhumi Dwarka: દ્વારકાના જગતમંદિરને મળ્યું વર્લ્ડ અમેઝિંગ પેલેસનું સન્માન|last=Gujarati|first=TV9|date=22 March 2021|work=Tv9 Gujarati|access-date=23 March 2021|language=gu|trans-title=Devbhumi Dwarka: Dwarka's Jagatmandir honored with World Amazing Palace}}</ref><ref>{{Cite web|url=https://www.worldtalentorg.com/worlds-amazing-place/shree-dwarkadhish-temple/|title=Shree Dwarkadhish Temple {{!}} World Talent Organization|date=13 March 2021|website=worldtalentorg.com|access-date=23 March 2021}}</ref> == ग्रंथसूची == * [http://www.indianexpress.com/ie/daily/20000714/ina14051.html UNESCO World Cultural Heritage Site status, report on Indian Express newspaper website] * [https://web.archive.org/web/20090112151303/http://www.nio.org/projects/vora/project_vora_1.jsp Underwater remains near Dwarakadheesh temple, on website of National Institute of Oceanography] == संदर्भ == {{Reflist|40em}} == बाहरी लिंक == * [http://www.dwarkadhish.org/ आधिकारिक वेबसाइट] [[श्रेणी:हिंदू मंदिर]] [[श्रेणी:चार धाम मंदिर]] ay4250ixwjplj10vyfral2pjkmos7uu औद्योगिक प्रशिक्षण संस्थान 0 88201 803340 786567 2026-07-29T16:22:02Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803340 wikitext text/x-wiki भारत में '''औद्योगिक प्रशिक्षण संस्थान''' भारत सरकार के श्रम आ रोजगार मंत्रालय के तहत चलावल जाए वाला प्रशिक्षण संस्थान हवें, जवना में प्रशिक्षु लोग के तकनीकी काम में प्रशिक्षित कइल जाला। ==== कारीगर प्रशिक्षण योजना (सीटीएस) ==== प्रशिक्षण महानिदेशालय (डीजीटी) 1950 में कारीगर प्रशिक्षण योजना के सुरुआत कइलस।[4] 130 से अधिका अलग-अलग विशेषता में प्रशिक्षण के अवधि छह महीना से लेके दु साल तक होखेला। कोर्स खातिर पूर्व शर्त 8वीं से लेके 12वीं तक के पास बा। प्रशिक्षण पूरा होखला प प्रशिक्षु अखिल भारतीय व्यापार परीक्षा (एआईटीटी) लिखेले। सफल उम्मीदवारन के राष्ट्रीय व्यापार प्रमाणपत्र (एनटीसी) दिहल जाला. ==== प्रवेश (दाखिला लेबे के बा) ==== आईटीआई/आईटीसी के विभिन्न ट्रेड में प्रवेश हर साल अगस्त में होला। नया सत्र शुरू होखे से पहिले आईटीआई प्रवेश प्रक्रिया शुरू हो जाला। एह योजना के तहत सत्र 1 अगस्त से शुरू हो रहल बा। एनसीवीटी गाइडलाइन के तहत आईटीआई में दाखिला मेरिट आधारित / लिखित परीक्षा प कइल जाला। निजी आईटीआई में प्रवेश सीधे होला लेकिन एकरा में खर्च सरकारी आईटीआई कॉलेज से अधिक होला। ==== आईटीआई ट्रेड्स के नाम ==== {| class="wikitable" |'''ट्रेड्स के नाम''' |'''ट्रेड कोड''' |- |Additive Manufacturing Technician Three D Printing (NSQF) |400 |- |ADVANCED CNC MACHINING TECHNICIAN (NSQF) |1001 |- |Aeronautical Structure and Equipment Fitter (NSQF) |401 |- |Agro Processing (NSQF) |402 |- |Architectural Assistant (NSQF) |403 |- |Architectural Draughtsman (NE) (NSQF) |405 |- |Architectural Draughtsman (NSQF) |990 |- |ARTISAN USING ADVANCED TOOL (NSQF) |1004 |- |Attendant Operator (Chemical Plant) (NSQF) |406 |- |Baker and Confectioner (NSQF) |407 |- |Bamboo Works (NSQF) |408 |- |BASIC DESIGNER AND VIRTUAL VERIFIER (MECHANICAL) (NSQF) |1005 |- |Cane Willow and Bamboo Worker (NSQF) |413 |- |Carpenter (NSQF) |966 |- |Catering & Hospitality Assistant (NSQF) |415 |- |CENTRAL AIRCONDITION PLANT MECHANIC(NSQF) |996 |- |Civil Engineer Assistant (NSQF) |416 |- |Computer Aided Embroidery And Designing (NSQF) |417 |- |Computer Hardware & Network Maintenance (NSQF) |418 |- |Computer Hardware & Networking Maintenance (One Year) (NSQF) |419 |- |Computer Operator & Programming Assistant (VI) (NSQF) |420 |- |Computer Operator and Programming Assistant |242 |- |Computer Operator and Programming Assistant (NSQF) |421 |- |Cosmetology (NSQF) |973 |- |Cosmetology (NSQF) |409 |- |COSMETOLOGY(NSQF) |999 |- |Craftsman Food Production (Vegetarian) (NSQF) |423 |- |Cutting & Sewing (VI) (NSQF) |425 |- |Dairying |100 |- |Dairying(NSQF) |993 |- |Data Base System Assistant (NSQF) |427 |- |Data Entry Operator (NSQF) |1012 |- |Dental Laboratory Equipment Technician (NSQF) |430 |- |Desk Top Publishing Operator (NSQF) |431 |- |Desktop Publishing Operator (VI) (NSQF) |432 |- |Digital Photographer (NSQF) |433 |- |Draftsman Civil (NSQF) |436 |- |Draughtsman (Civil) (DA) (NSQF) |438 |- |Draughtsman (Civil) (NSQF) |991 |- |Draughtsman (Mechanical) (NSQF) |439 |- |Dress Making (NSQF) |440 |- |Driver Cum Mechanic (NSQF) |1011 |- |Early Childhood Educator |544 |- |Early Childhood Educator(NSQF) |986 |- |Electrician |231 |- |Electrician (DST) (NSQF) |443 |- |Electrician (NSQF) |442 |- |Electrician Power Distribution (NSQF) |444 |- |Electronics Mechanic (NSQF) |446 |- |Electroplater |233 |- |Electroplater (NSQF) |447 |- |Event Management Assitant (NSQF) |1016 |- |Fashion Design & Technology (NSQF) |449 |- |Fibre to Home Technician (NSQF) |1017 |- |Finance Executive (NSQF) |450 |- |Fire Technology and Industrial Safety Management (NSQF) |451 |- |Firemen (NSQF) |452 |- |Fitter |227 |- |Fitter (DST) (NSQF) |454 |- |Fitter (NSQF) |453 |- |Floriculture & Landscaping (NSQF) |455 |- |Food & Beverages Services Assistant (NSQF) |456 |- |Food Beverage (NSQF) |457 |- |Food Production (General) (NSQF) |458 |- |Footwear maker (NSQF) |459 |- |Foundryman |214 |- |Foundryman (NSQF) |460 |- |Front Office Assistant (NSQF) |461 |- |Fruit and Vegetable Processor (NSQF) |462 |- |Geo Informatics Assitant (NSQF) |463 |- |Hair & Skin Care (VI) (NSQF) |464 |- |Health Safety & Environment (NSQF) |466 |- |Health Sanitary Inspector (NSQF) |467 |- |Horticulture (NSQF) |468 |- |Horticulture (P) |921 |- |Hospital House Keeping (NSQF) |470 |- |House Keeper (NSQF) |471 |- |Human Resource Executive (NSQF) |472 |- |Industrial Painter (NSQF) |473 |- |INDUSTRIAL ROBOTICS AND DIGITAL MANUFACTURING TECHNICIAN (NSQF) |1006 |- |Information Communication Technology System Maintenance (NSQF) |474 |- |Information Technology (NSQF) |475 |- |Instrument Mechanic (Chemical Plant) (NSQF) |478 |- |Instrument Mechanic (NSQF) |477 |- |Interior Decoration and Designing (P) |926 |- |Interior Design & Decoration (NSQF) |481 |- |IoT Technician (Smart Agriculture)(NSQF) |987 |- |IoT Technician (Smart City)(NSQF) |965 |- |IoT Technician (Smart healthcare)(NSQF) |988 |- |Laboratory Assistant (Chemical Plant) (NSQF) |485 |- |Laboratory Assistant-Chemical Plant (NSQF) |486 |- |Leather Goods Maker (NSQF) |487 |- |Lift and Escalator Mechanic (NSQF) |490 |- |Lift Mechanic (P) |929 |- |Litho- Offset Machine Minder (NSQF) |492 |- |Machinist (Grinder) (NSQF) |494 |- |Machinist (NSQF) |493 |- |Maintenance Mechanic (Chemical Plant) (NSQF) |495 |- |MANUFACTURING PROCESS CONTROL AND AUTOMATION (NSQF) |1007 |- |Marine Engine Fitter (NSQF) |496 |- |Marine Fitter (NSQF) |497 |- |Marketing Executive (NSQF) |498 |- |Mason (Building Constructor) (NSQF) |499 |- |Mech. Repair & Maintenance of Heavy Vehicles (NSQF) |500 |- |Mech. Repair & Maintenance of Light Vehicles (NSQF) |501 |- |Mechanic (Motor Vehicle) (NSQF) |502 |- |Mechanic (Refrigeration and Air-Conditioning) (NSQF) |503 |- |Mechanic (Tractor) (NSQF) |504 |- |Mechanic Agriculture Machinery (NSQF) |505 |- |Mechanic Auto Body Painting (NSQF) |507 |- |Mechanic Auto Body Repair (NSQF) |508 |- |Mechanic Auto Electrical and Electronics (NSQF) |509 |- |Mechanic Communication Equipment Maintenance (NSQF) |510 |- |Mechanic Computer Hardware (NSQF) |511 |- |Mechanic Consumer Electronics (NSQF) |512 |- |Mechanic Consumer Electronics Appliances (NSQF) |513 |- |Mechanic Cum Operator Electronics Communication System (NSQF) |514 |- |Mechanic Diesel (NSQF) |515 |- |MECHANIC ELECTRIC VEHICLE (NSQF) |1002 |- |Mechanic Industrial Electronics (NSQF) |516 |- |Mechanic Lens/Prism Grinding (NSQF) |517 |- |Mechanic Machine Tool Maintenance (NSQF) |518 |- |Mechanic Mechatronics (NSQF) |519 |- |Mechanic Medical Electronics (NSQF) |520 |- |Mechanic Mining Machinery (NSQF) |521 |- |Mechanic Radio & T.V. (NSQF) |523 |- |Mechanic Two & Three Wheeler (NSQF) |985 |- |Metal Cutting Attendant (VI) (NSQF) |527 |- |Milk & Milk Product Technician (NSQF) |994 |- |Multimedia Animation & Special Effects (NSQF) |530 |- |Network Technician (NSQF) |1015 |- |Office Assitant cum Computer Operator (NSQF) |532 |- |Old Age Care (NSQF) |533 |- |Operator Advanced Machine Tools (NSQF) |535 |- |Painter General (NSQF) |536 |- |Photographer (NSQF) |539 |- |Physiotherapy Technician (NSQF) |540 |- |Plastic Processing Operator (NSQF) |541 |- |Plate Maker-Cum-Impositor (NSQF) |542 |- |Plumber (NSQF) |543 |- |Pump Operator-Cum-Mechanic (NSQF) |547 |- |Radiology Technician (NSQF) |548 |- |REFRIGERATION AND AIR CONDITIONING TECHNICIAN(NSQF) |998 |- |Remotely Piloted Aircraft (RPA Drone Pilot) (NSQF) |1014 |- |Secretarial Practice (English) |258 |- |Secretarial Practice (English) (NSQF) |553 |- |Sewing Technology (NSQF) |554 |- |Sheet Metal Worker (DA) (NSQF) |556 |- |Sheet Metal Worker (NSQF) |555 |- |Smartphone Technician Cum App Tester (NSQF) |1013 |- |Soil Testing and Crop Technician (NSQF) |559 |- |Solar Technician |949 |- |Solar Technician (Electrical) (NSQF) |561 |- |Solar Technician (NSQF) |560 |- |Spa Therapy (NSQF) |562 |- |Spinning Technician (NSQF) |563 |- |Stenographer & Secretarial Assistant (English) (NSQF) |564 |- |Stenographer & Secretarial Assistant (Hindi) (NSQF) |565 |- |Stone Mining Machine Operator (NSQF) |566 |- |Stone Processing Machine Operator (NSQF) |567 |- |Surface Ornamentation Techniques (Embroidery) (NSQF) |568 |- |Surveyor |207 |- |Surveyor (NSQF) |970 |- |Surveyor (Two Years) |974 |- |Technician Mechatronics (NSQF) |570 |- |TECHNICIAN MEDICAL ELECTRONICS(NSQF) |995 |- |Technician Power Electronics System (NSQF) |571 |- |Textile Mechatronics (NSQF) |572 |- |Textile Wet Processing Technician (NSQF) |573 |- |Tool & Die Maker (Dies & Moulds) (NSQF) |574 |- |Tool & Die Maker (Press Tools, Jigs & Fixtures) (NSQF) |575 |- |Tourist Guide (NSQF) |576 |- |Travel & Tour Assistant (NSQF) |577 |- |Turner (NSQF) |578 |- |Vessel Navigator (NSQF) |579 |- |Weaving of Woolen Fabrics (NSQF) |580 |- |Weaving Technician (NSQF) |581 |- |Weaving Technician for Silk & Woolen Fabrics (NSQF) |582 |- |Welder |312 |- |Welder (DA) (NSQF) |585 |- |Welder (Fabrication & Fitting) (NSQF) |586 |- |Welder (GMAW & GTAW) (NSQF) |992 |- |Welder (NSQF) |969 |- |Welder (Pipe) (NSQF) |588 |- |Welder (Structural) (NSQF) |589 |- |Welder (Welding & Inspection) (NSQF) |590 |- |Wireman (NSQF) |592 |} ==== एनसीवीटी आईटीआई सीबीटी परीक्षा के तैयारी कईसे कइल जाला? ==== ई सवाल ओह विद्यार्थियन से उठत बा. जे आईटीआई एडमिशन लेले बा आ अब उनकर परीक्षा नजदीक आ गइल बा। वैसे हम बता दीं कि हम आईटीआई भी कइले बानी। हम एनसीवीटी आईटीआई सीबीटी परीक्षा के भी तैयारी ऑनलाइन कइले रहनी, उहो मुफ्त में, हालांकि रउआ कई तरह से ऑनलाइन तैयारी कर सकतानी। बाकिर जवना वेबसाइट से हम तइयारी करत रहीं ओकर नाम रहे [https://www.testgive.com/ TestGive.com] {{Webarchive|url=https://web.archive.org/web/20230922155127/https://www.testgive.com/ |date=2023-09-22 }} इहाँ से मॉक टेस्ट देके रउआ अपना परीक्षा के तैयारी कर सकतानी। बता दीं कि आईटीआई सीबीटी परीक्षा के तइयारी खातिर Bharat Skills पर निमी क्वेश्चन बैंक जारी होला. ऊ सब सवाल एह मॉक टेस्ट में रहेला। ==== शिक्षुता प्रशिक्षण योजना ==== आईटीआई पूरा कइला के बाद व्यापारी अलग-अलग उद्योग में अप्रेंटिसशिप के प्रशिक्षण ले सकतारे। एह योजना के कौशल विकास आ उद्यमिता मंत्रालय के तहत प्रशिक्षण महानिदेशालय द्वारा एह उद्देश्य से लागू कइल जाला कि "कौशल हासिल करे खातिर अकेले संस्थान सभ में दिहल जाए वाला प्रशिक्षण पर्याप्त ना होला आ वास्तविक कामकाजी जगह पर प्रशिक्षण से एकर पूरक होखे के जरूरत बा। अप्रेंटिसशिप प्रशिक्षण के अवधि आम तौर प आईटीआई के अंतिम परीक्षा में आवे वाला लोग खातिर एक साल के होखेला, यानी एआईटीटी। फ्रेशर लोग खातिर आमतौर पर तीन साल लागेला। प्रशिक्षण के दौरान प्रशिक्षु लोग के हर महीना वजीफा दिहल जाला। o86trs6z99bms3chhrjdrxoi8b90j96 बद्रीनाथ मंदिर 0 88235 803368 783706 2026-07-29T16:24:13Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803368 wikitext text/x-wiki '''बद्रीनाथ''' या '''बद्रीनारायण मंदिर''' [[विष्णु]] के समर्पित एगो [[हिंदू मंदिर]] ह। ई [[उत्तराखंड|भारत के उत्तराखंड]] राज्य के बद्रीनाथ शहर में स्थित बा। ई मंदिर विष्णु के समर्पित 108 दिव्य देश में से एगो भी बा - वैष्णव लोग खातिर पवित्र तीर्थ - जिनके बद्रीनाथ के रूप में पूजल जाला। ई हर साल छह महीना (अप्रैल के अंत से नवंबर के सुरुआत के बीच) खुलल रहे ला, काहें से कि [[हिमालय|हिमालयी]] इलाका में मौसम के चरम स्थिति होला। ई मंदिर [[चमोली जिला]] के गढ़वाल पहाड़ी पटरी में [[अलकनंदा]] नदी के किनारे बा। ई भारत के सभसे ढेर यात्रा कइल जाए वाला तीर्थ केंद्र सभ में से एक बाटे, 2022 में महज 2 महीना में 28 लाख (28 लाख) लोग के दौरा इहाँ भइल बाटे।<ref>{{Cite news|url=https://timesofindia.indiatimes.com/videos/toi-original/uttarakhand-28-lakh-pilgrims-visit-char-dham-in-60-days-choppers-flying-like-auto-rickshaws-experts-warn-of-consequences/videoshow/93475084.cms|title=Uttarakhand: 28 lakh pilgrims visit Char Dham in 60 days, choppers flying like auto-rickshaws, experts warn of consequences|date=10 August 2022|work=The Times of India|access-date=12 August 2022}}</ref> {{Infobox Hindu temple |name = बद्रीनाथ मंदिर |image = Badrinath Temple , Uttarakhand.jpg |alt = Image of the temple with steps leading to the entrance |caption = The temple with steps leading to the entrance |coordinates ={{coord|30.744695|79.491175|type:landmark_region:IN|display=inline,title}} |map_type = India Uttarakhand |map_caption = उत्तराखंड में स्थिति |country = {{IND}} |state = [[Uttarakhand]] |district = [[Chamoli district]] |location = [[Badrinath]] |elevation_m = 3100 |architecture = |inscriptions = |website = {{url|badrinath-kedarnath.gov.in}} }} मंदिर में पूजल जाए वाला मुख्य देवता के मुर्ती एगो {{Convert|1|ft|abbr=on}}, बद्रीनारायण के रूप में विष्णु के करिया ग्रेनाइट के बनल मुर्ती हवे। एह देवता के कई हिंदू लोग आठ गो ''स्वयं व्यक्त क्षेत्र'', या विष्णु के स्व-प्रकट देवता सभ में से एक माने ला। [[गंगा नदी|गंगा]] नदी के धरती माई पर उतरे के याद करे वाला "माता मूर्ति क मेला" बद्रीनाथ मंदिर में मनावल जाए वाला सबसे प्रमुख परब ह। हालांकि बद्रीनाथ उत्तर भारत में स्थित बा, लेकिन मुख्य पुजारी या रावल परंपरागत रूप से दक्षिण भारतीय राज्य [[केरल]] से चुनल नम्बुदिरी ब्राह्मण होखे लें। एह मंदिर के [[उत्तर प्रदेश]] राज्य सरकार के एक्ट नंबर 30/1948 में एक्ट नंबर 16,1939 में शामिल कइल गइल जवना के बाद में श्री बदरिनाथ आ श्री [[केदारनाथ मंदिर|केदारनाथ]] मंदिर एक्ट के नाम से जानल गइल। राज्य सरकार के ओर से नामित समिति दुनो मंदिर के प्रबंधन करेले अउरी एकरा बोर्ड में सतरह सदस्य बाड़े। [[चित्र:Devotees_at_the_Badrinath_Temple_in_Uttarakhand,_India.jpg|thumb|अक्टूबर 2022 में बद्रीनाथ मंदिर में भक्त]] ''विष्णु पुराण'' आ ''स्कंद पुराण'' जइसन प्राचीन धार्मिक ग्रंथन में एह मंदिर के जिक्र बा। एकर महिमा ''नालायिरा दिव्य प्रबंधम'', 6वीं–9वीं सदी ईसवी के अल्वार संत लोग के सुरुआती मध्यकालीन तमिल ग्रंथ में बर्णित बा। == स्थान, वास्तुकला, आ तीर्थ सभ == [[चित्र:Badrinath-taptkund.jpg|alt=a rectangular tank with a house on the bank and with people taking bath in the hot springs|left|thumb|200x200px| बद्रीनाथ मंदिर के बगल में तप्त कुंड हॉट स्प्रिंग्स, स्नान घर के अंदर]] ई मंदिर [[उत्तराखंड]] के [[चमोली जिला]] में [[अलकनंदा|अलकनंदा नदी]] {{Sfn|Baynes|1878}} के किनारे गढ़वाल पहाड़ी इलाका में बा। पहाड़ी इलाका औसत समुद्र तल से ऊपर {{Convert|3133|m|ft|abbr=on}} पर स्थित बा। <ref name="about"/> मंदिर के सामने नर परबत पर्वत बा, जबकि नीलकंठ चोटी के पीछे नारायण परबत बा। आदि शंकर नौवीं सदी में बद्रीनाथ के तीर्थ स्थल के रूप में स्थापित कइलें। मंदिर के तीन गो संरचना बा: गर्भगृह, [[हिंदू दर्शन|दर्शन]] मंडप (पूजा भवन), आ सभा मंडप (सम्मेलन हॉल)। <ref name="about"/> गर्भगृह के शंक्वाकार आकार के छत, ''गर्भगृह'', लगभग {{Convert|15|m|ft|abbr=on}} लंबा बाटे आ ऊपर से एगो छोट गुंबद बा जे सोना के मढ़ल छत से ढंकल बा। <ref name="Nair67" /> सोझा के मुख्य साइड पत्थर से बनल बा आ मेहराबदार खिड़की बाड़ी सऽ। एगो चौड़ा सीढ़ी मुख्य प्रवेश द्वार पर पहुँचे ले, ई एगो ऊँच, मेहराबदार गेटवे हवे। ठीक भीतर एगो मंडप, एगो बड़हन, खंभा वाला हॉल बा जे गर्भगृह में जाला। हॉल के देवाल आ खंभा पर जटिल नक्काशी कइल गइल बा। गरभगृह में {{Convert|1|ft|abbr=on}} के बद्रीनारायण के शालीग्राम (काला पत्थर के) मुर्ती बाटे जवन बद्री के पेड़ के नीचे सोना के छतरी में रखल बा। बद्रीनारायण के देवता में ऊ अपना दू गो बाँहि में ''[[शंख]]'' (शंख) आ ''[[सुदर्शन चक्र|चक्र]]'' (चक्र) के उठावल मुद्रा में आ बाकी दू गो बाँह ''योगमुद्रा'' ( ''पद्मासन'' ) मुद्रा में गोदी में टिकल देखावल गइल बा। <ref name="about">{{Cite web|url=http://www.badarikedar.org/badrinath.aspx|title=About the temple|year=2006|publisher=Shri Badrinath - Shri Kedarnath Temples Committee|archive-url=https://web.archive.org/web/20131214005611/http://badarikedar.org/badrinath.aspx|archive-date=14 December 2013|access-date=1 January 2014}}</ref> एह गर्भगृह में धन के देवता {{Em dash}} कुबेर, नारद ऋषि, उद्धव, नर आ नारायण के मूर्ति भी बाड़ी स। पन्द्रह गो अउरी मूर्ति बाड़ी सऽ जिनहन के पूजा मंदिर के आसपास कइल जाला। इनहन में [[लक्ष्मी]] (विष्णु के पत्नी), गरुड़ (नारायण के वाहन ), आ [[नव दुर्गा|नवदुर्गा]], [[दुर्गा]] के नौ गो अलग-अलग रूप में प्रकटीकरण सामिल बाड़ें। मंदिर में लक्ष्मी नरसिंहर के तीर्थ आ संत लोग खातिर [[आदि शंकर]] (ई.स&nbsp;788-820), नर आ नारायण, घंटकरन, वेदांत देसिका आ रामानुजाचार्य के अस्थान बाटे। मंदिर के सभ देवता करिया पत्थर से बनल बाड़ें। <ref name="about" /> मंदिर के ठीक नीचे गरम सल्फर के झरना सभ के समूह तप्त कुंड के औषधीय परभाव वाला मानल जाला; कई तीर्थयात्री लोग मंदिर में जाए से पहिले झरना में नहाए के जरूरत मानेला। झरना सभ के तापमान साल भर {{Convert|55|°C|°F|abbr=on}} होला, जबकि बाहरी तापमान आमतौर पर {{Convert|17|°C|°F|abbr=on}} से नीचे होला। <ref name="about"/> मंदिर में दू गो पानी के पोखरा के नारद कुंड आ सूर्य कुंड कहल जाला। == इतिहास ==   मंदिर के बारे में कवनो ऐतिहासिक रिकार्ड नइखे, बाकिर [[वेद|वैदिक ग्रंथन]] में एहिजे के मुख्य देवता बद्रीनाथ के उल्लेख मिले ला ( {{Circa|1750|lk=no}} –500&nbsp;ईसा पूर्व के बा)। कुछ विवरणन के अनुसार [[वैदिक काल]] में एह तीर्थ में कवनो ना कवनो रूप में पूजा कइल जात रहे। ई मंदिर 8वीं सदी ले बौद्ध तीर्थ रहल आ [[आदि शंकर]] एह तीर्थ के फेर से जिंदा क के हिंदू मंदिर में बदल दिहलें। मंदिर के आर्किटेक्चर [[बौद्ध धर्म|बौद्ध]] विहार से मिलत जुलत आ चमकदार रंग से रंगल साम्हने के साइड जे बौद्ध मंदिर सभ के बिसेसता हवे, एह तर्क के पुष्टी करे ला। अउरी बिबरन सभ में बतावल गइल बा कि मूल रूप से एकर स्थापना आदिशंकर द्वारा नौवीं सदी में तीर्थस्थल के रूप में कइल गइल रहे। मानल जाला कि शंकर ई.&nbsp;814 से 820 तक छह साल ले एह जगह पर रहलें। छह महीना बद्रीनाथ में आ बाकी साल केदारनाथ में रहलन। हिंदू अनुयायी लोग के कहनाम बा कि ऊ अलकनंदा नदी में बद्रीनाथ के देवता के मुर्ती के खोज कइलें आ एकरा के तप्त कुंड हॉट स्प्रिंग्स के लगे एगो गुफा में अस्थापित कइलें। एगो पारंपरिक कहानी में ई दावा कइल गइल बा कि शंकर एह इलाका के सगरी बौद्ध लोग के परमार शासक राजा कनक पाल के मदद से भगा दिहलें। राजा के वंशानुगत उत्तराधिकारी लोग मंदिर के शासन करत रहे आ एकर खरचा पूरा करे खातिर गाँव के रकम देवे। मंदिर के रास्ता पर पड़े वाला गाँव सभ के एगो समूह से मिले वाला आमदनी के इस्तेमाल तीर्थयात्री लोग के खाना खियावे आ रहे खातिर कइल जात रहे। परमार के शासक लोग "बोलंदा बद्रीनाथ" के उपाधि रखले रहे, मतलब बोले वाला बद्रीनाथ। इनकर अउरी उपाधि रहे जवना में श्री 108 बसदृष्टाचार्यपरायण गढ़राज महिमाहेन्द्र, धर्मबीभाब, आ धर्मरक्षक सिगमानी शामिल रहे। बद्रीनाथ के सिंहासन के नाँव एहिजा के मुख्य देवता के नाँव पर रखल गइल; राजा तीर्थ के ओर जाए से पहिले भक्त लोग के विधिवत नमन के आनंद लेत रहले। 19वीं सदी के अंत तक ई प्रथा जारी रहल। 16वीं सदी के दौरान गढ़वाल के राजा मूर्ती के वर्तमान मंदिर में ले गइलें। जब गढ़वाल राज्य के बंटवारा भइल त बद्रीनाथ मंदिर अंग्रेज के शासन में आ गइल लेकिन गढ़वाल के राजा प्रबंधन समिति के अध्यक्ष के रूप में जारी रहलें। पुजारी के चयन गढ़वाल आ त्रावणकोर राजपरिवार के बीच सलाह-मशविरा के बाद कइल जाला। <ref name="Guha" /> मंदिर के उमिर आ हिमस्खलन से नोकसान के कारन एह मंदिर के कई गो बड़हन मरम्मत भइल बा। 17वीं सदी में गढ़वाल के राजा लोग द्वारा मंदिर के बिस्तार कइल गइल। 1803 के गढ़वाल के भयानक भूकंप के दौरान काफी नुकसान के बाद एकर बहुत हद तक जयपुर के राजा द्वारा दोबारा बनावल गइल। एकर मरम्मत अबहिन ले 1870 के दशक में भइल रहे{{Sfn|Baynes|1878}} बाकी [[पहिला बिस्व जुद्ध]] के समय ले ई सभ पूरा हो गइल रहलें। {{Sfn|Chisholm|1911}} ओह समय ई शहर अबहिन छोट रहल, एह में खाली 20-अजीब झोपड़ी सभ रहलीं जिनहन में मंदिर के कार्यकर्ता पंडा लोग रहे, बाकी तीर्थयात्री लोग के संख्या आमतौर पर सात से दस हजार के बीच रहे।{{Sfn|Chisholm|1911}} हर बारह साल पर होखे वाला कुंभा मेला महोत्सव में आगंतुकन के संख्या बढ़ के 50,000 हो गइल।{{Sfn|Chisholm|1911}} एह मंदिर के बिबिध राजा लोग द्वारा वसीयत कइल गइल बिबिध गाँव सभ के किराया से भी राजस्व मिलत रहे। {{Sfn|Baynes|1878}} साल 2006 के दौरान राज्य सरकार अवैध अतिक्रमण प लगाम लगावे खाती बद्रीनाथ के आसपास के इलाका के नो कंस्ट्रक्शन जोन के घोषणा कइलस। == दंतकथा == [[चित्र:Nara_narayana.jpg|thumb|नर आ नारायण]] हिंदू कथा के अनुसार देवता [[विष्णु]] एही जगह पर ध्यान में बइठल रहलें। ध्यान के दौरान विष्णु ठंडा के मौसम से अनजान रहले। [[लक्ष्मी]], उनकर पत्नी, बदरी के पेड़ (जेकरा के भोजपुरी में 'बइर' कहल जाला) के रूप में उनकर रक्षा कइली। लक्ष्मी के भक्ति से प्रसन्न होके विष्णु एह जगह के नाम ''बद्रिका आश्रम रखले'' । एटकिन्सन (1979) के अनुसार ई जगह पहिले बइर के जंगल रहे, जवन आज उहाँ ना पावल जाला। मंदिर में बद्रीनाथ के रूप में विष्णु के ''पदमासन'' मुद्रा में बइठल चित्रित कइल गइल बा। किंवदंती के अनुसार विष्णु के नारद ऋषि ताड़ दिहले रहले, जे विष्णु के पत्नी लक्ष्मी के गोड़ के मालिश करत देखले रहले। विष्णु तप करे खातिर बद्रीनाथ गइलें, बहुत देर तक पदमासन में ध्यान कइलें। ''विष्णु पुराण'' में बद्रीनाथ के उत्पत्ति के एगो अउरी संस्करण बतावल गइल बा। परंपरा के अनुसार यम के दू गो बेटा नरा आ नारायण रहलें {{Em dash}} ई दुनों हिमालय के पहाड़ सभ के आधुनिक नाँव हवें। ऊ लोग आपन धर्म फइलावे खातिर जगह चुनल आ हर केहू हिमालय के विशाल घाटी से बियाह कइल। आश्रम के स्थापना खातिर आदर्श जगह खोजत घरी ओह लोग के पंच बदरी के बाकी चार गो बद्री, अर्थात बृधा बद्री, योग बद्री, ध्यान बद्री आ भविष्य बद्री से भेंट भइल। आखिरकार अलकनंदा नदी के पीछे के गरम ठंडा झरना मिल गइल अउरी एकर नाम '''"''' बद्री विशाल" रखलें। == तीर्थ यात्रा == {{Char Dham}} {{Historical populations|1990|362757|1995|275900|2000|735200|2005|566524|2010|921950|2012|1060000}} हर आस्था आ हिंदू धर्म के सभ विचारधारा के भक्त लोग बद्रीनाथ मंदिर में आवेलें। <ref>[[Badrinath Temple#Rao|Rao 2008]], p. 474</ref> काशी मठ,<ref>{{Cite web|url=http://www.kashimath.org/branches/|title=Kashi Math at Badrinath|publisher=Shree Kashi Math Samasthanam|access-date=10 September 2014|archive-date=8 August 2018|archive-url=https://web.archive.org/web/20180808203059/https://www.kashimath.org/branches/|url-status=dead}}</ref> जेयर मठ (आंध्र मठ),<ref>{{Cite web|url=http://www.chinnajeeyar.org/main/content/badari-ashtakshari-kshethriya-annadana-sakha-sangham-bakass/|title=Badari Ashtakshari Kshethriya Annadana Sakha Sangham, (BAKASS)|publisher=Chinna Jeeyar Mutt|archive-url=https://web.archive.org/web/20140910201610/http://www.chinnajeeyar.org/main/content/badari-ashtakshari-kshethriya-annadana-sakha-sangham-bakass/|archive-date=10 September 2014|access-date=10 September 2014}}</ref> उडुपी पेजावर <ref>{{Cite web|url=http://www.vishveshavani.com/contact/|title=Udupi Mutt at Badrinath|publisher=Pejavara Adhokshaja Matha, Udupi|access-date=10 September 2014|archive-date=8 August 2018|archive-url=https://web.archive.org/web/20180808171742/http://www.vishveshavani.com/contact/|url-status=dead}}</ref> आ मंतरलायम श्री राघवेन्द्र स्वामी मठ <ref>{{Cite web|url=http://www.raghavendramutt.org/node/38|title=Raghavendra Mutt Branches|publisher=Raghavendra Mutt|archive-url=https://web.archive.org/web/20140922161955/http://www.raghavendramutt.org/node/38|archive-date=22 September 2014|access-date=10 September 2014}}</ref> नियर सगरी प्रमुख मठ संस्थान सभ के शाखा आ गेस्ट हाउस एहिजा बा। बद्रीनाथ मंदिर पाँच बदरी नाँव के पाँच गो संबंधित तीर्थ सभ में से एक हवे जे विष्णु के पूजा खातिर समर्पित बाड़ें। पांच मंदिर बा विशाल बद्री - बद्रीनाथ में बद्रीनाथ मंदिर, पांडुकेश्वर में स्थित योगध्यान बद्री, {{Convert|17|km|mi|1|abbr=on}} स्थित भविष्य बद्री ज्योतिर्मथ से सुबैन, वृध बद्री स्थित {{Convert|7|km|mi|1|abbr=on}} अनिमाठ में ज्योतिर्मथ से आ आदि बद्री से {{Convert|17|km|mi|1|abbr=on}} [[कर्णप्रयाग]] से मिलल बा। ई मंदिर सभसे पबित्र हिंदू ''चार धाम'' (चार गो दिव्य) स्थल सभ में से एक मानल जाला, जहाँ रामेश्वरम, बद्रीनाथ, [[पुरी]] आ द्वारका सामिल बाड़ें। हालांकि मंदिर के उत्पत्ति के बारे में साफ-साफ जानकारी नइखे, लेकिन आदिशंकर के ओर से स्थापित हिंदू धर्म के अद्वैत स्कूल चार धाम के उत्पत्ति के श्रेय द्रष्टा के बतावेले। ई चारो मठ भारत के चारो कोना में बाड़ें आ इनहन के अनुचर मंदिर उत्तर में बद्रीनाथ में बद्रीनाथ मंदिर, पूरब में पुरी में [[जगन्नाथ मंदिर, पुरी|जगन्नाथ मंदिर]], पश्चिम में द्वारका में [[द्वारिकाधीश मंदिर|द्वारकाधीश मंदिर]] आ दक्खिन में रामेश्वरम, [[तमिलनाडु]] में रामेश्वरम बाड़ें। <ref name="Chakra" /><ref name="Mittal" /> भले वैचारिक रूप से मंदिर सभ हिंदू धर्म के संप्रदाय, यानी [[शैव मत|शैव]] आ वैष्णव धर्म के बीच बँटल बाड़ें, चार धाम के तीर्थयात्रा एगो अखिल हिन्दू मामला हवे। हिमालय में चार गो निवासस्थान बाड़ें जिनहन के छोट चार धाम कहल जाला: बद्रीनाथ, केदारनाथ, [[गंगोत्री]] आ यमुनोत्री {{Em dash}} ई सभ हिमालय के तलहटी में पड़े लें। मूल चार धाम सभ के बिभेद करे खातिर 20वीं सदी के बीच में ''छोटा'' नाँव जोड़ल गइल। आधुनिक समय में जइसे-जइसे एह जगहन के तीर्थयात्री लोग के संख्या बढ़ल बा, एकरा के हिमालयन चार धाम कहल जाला। भारत के चार गो कार्डिनल प्वाइंट के पार के यात्रा के हिन्दू लोग पवित्र मानेला, जे लोग अपना जीवन में एक बेर एह मंदिरन के दौरा करे के आकांक्षा राखेला। परंपरागत रूप से ई तीर्थयात्रा पुरी से पूरबी छोर से शुरू होले आ घड़ी के दिशा में अइसन तरीका से आगे बढ़े ले जेकर पालन आमतौर पर हिंदू मंदिर सभ में परिक्रमा खातिर कइल जाला। <ref name="Gwynne" /> == परब-त्यौहार आ धार्मिक प्रथा == [[चित्र:Badri_Night.JPG|alt=image of the temple at night illuminated with green light|thumb| रात में बद्रीनाथ मंदिर]] बद्रीनाथ मंदिर में आयोजित सबसे प्रमुख परब माता मूर्ति का मेला ह जवन [[गंगा नदी|गंगा]] नदी के धरती माई प उतरे के याद में मनावल जाला। पार्थिव प्राणी सब के भलाई खातिर नदी के बारह नाला में बाँटल मानल जाए वाली बद्रीनाथ के महतारी के पूजा एह परब में कइल जाला। जहाँ नदी बहत रहे उ जगह बद्रीनाथ के पवित्र भूमि बन गइल। <ref name="festivals">{{Cite web|url=http://www.badarikedar.org/content-badari.aspx?id=11|title=Festivals celebrated in the temple|year=2006|publisher=Shri Badrinath - Shri Kedarnath Temples Committee|archive-url=https://web.archive.org/web/20120217172228/http://www.badarikedar.org/content-badari.aspx?id=11|archive-date=17 February 2012|access-date=1 January 2014}}</ref> [[Badri Kedar|बदरी केदार]] परब जून महीना में मंदिर आ केदारनाथ मंदिर दुनों में मनावल जाला। ई परब आठ दिन ले चले ला; एह दौरान देश भर के कलाकार आपन प्रस्तुति देवे लें।<ref name="festivals"/> रोज सबेरे होखे वाला प्रमुख धार्मिक गतिविधि (या ''पूजा'' ) में ''महाभिषेक'', ''अभिषेक, गीतापाठ'' ''आ'' ''भागवत पूजा'' होला जबकि शाम के ''पूजा'' में ''गीत गोविंद'' आ ''आरती'' शामिल होला। ''अष्टोत्रम'' आ ''सहस्रनाम'' जइसन वैदिक स्त्रोत पाठ सगरी संस्कारन के दौरान कइल जाला। ''आरती'' के बाद बद्रीनाथ के छवि से सजावट हटा के ओकरा पर चंदन के लेप लगावल जाला। छवि से लेप अगिला दिने ''निर्मलय [[हिंदू दर्शन|दर्शन]]'' के दौरान प्रसाद के रूप में भक्त लोग के दिहल जाला। कुछ हिंदू मंदिरन के बिपरीत सभ संस्कार भक्तन के सोझा कइल जाला, जहाँ कुछ प्रथा ओह लोग से छिपल रहेला। चीनी के गोला आ सूखल पतई भक्त लोग के दिहल आम प्रसाद ह। मई 2006 से स्थानीय रूप से तैयार आ स्थानीय बांस के टोकरी में पैक कइल पंचमृत प्रसाद के चढ़ावे के प्रथा शुरू हो गइल। मंदिर [[Bhatridwityia or later|भतृद्वितिया के शुभ दिन भा बाद]] में अक्टूबर–नवंबर के दौरान जाड़ा खातिर बंद रहेला। <ref name="close" /> बंद होखे के दिन ''अखंड ज्योति'', छह महीना तक चले खातिर घीव भर के दीप जरावल जाला। <ref name="Bhalla258" /> ओह दिन मुख्य पुजारी द्वारा तीर्थयात्री आ मंदिर के अधिकारी लोग के मौजूदगी में विशेष ''पूजा'' कइल जाला। <ref>{{Cite news|url=http://zeenews.india.com/news/uttarakhand/badrinath-shrine-closes-marking-end-of-chardham-yatra_742346.html|title=Badrinath shrine closes marking end of Chardham Yatra|date=17 November 2011|access-date=28 April 2014|publisher=Zeenews|location=Dehradun}}</ref> बद्रीनाथ के छवि के काल्पनिक रूप से एह दौरान ज्योतिर्मठ के नरसिंह मंदिर में स्थानांतरित कइल जाला, जवन {{Convert|40|mi|km|abbr=on}} मंदिर से दूर बा। मंदिर के अप्रैल–मई के आसपास अक्षय तृतीया के फेर से खोलल जाला, जवन [[हिंदू पतरा|हिन्दू कैलेंडर]] के एगो अउरी शुभ दिन ह। <ref name="close">{{Cite news|url=http://timesofindia.indiatimes.com/india/Badrinath-shrine-closed-for-winter/articleshow/3724410.cms|title=Badrinath shrine closed for winter|date=18 November 2008|work=TNN|access-date=28 April 2014|publisher=The Times of India|location=Dehradun}}</ref> जाड़ा के बाद मंदिर के उद्घाटन के पहिला दिन तीर्थयात्री लोग ''अखंड ज्योति'' के साक्षी बने खातिर जुटेला। ई मंदिर ओह पवित्र जगहन में से एगो ह जहाँ हिंदू लोग पुजारी लोग के मदद से पुरखा लोग के चढ़ावा चढ़ावेला। भक्त लोग मंदिर में जाके गर्भगृह में बद्रीनाथ के प्रतिमा के सामने पूजा करेला अउरी अलकनंदा नदी में पवित्र डुबकी लगावेला। आम मान्यता बा कि डुबकी लगावे से आत्मा शुद्ध हो जाला। == प्रशासन आ भ्रमण == [[चित्र:Alaknanda.jpg|alt=image of Alaknanda river with a hanging bridge across it|left|thumb|200x200px| बद्रीनाथ में [[अलकनंदा|अलकनंदा नदी]]]] बद्रीनाथ मंदिर के उत्तर प्रदेश राज्य सरकार अधिनियम संख्या 30/1948 में अधिनियम संख्या 16,1939 के रूप में शामिल कइल गइल। जवना के बाद में श्री बदरिनाथ आ श्री केदारनाथ मंदिर एक्ट के नाम से जानल गइल। उत्तराखंड राज्य सरकार के ओर से नामित एगो समिति दुनो मंदिर के प्रबंधन करेले। एह कानून में 2002 में संशोधन क के अतिरिक्त समिति सदस्यन के नियुक्ति कइल गइल जेह में सरकारी अधिकारी आ एगो उपाध्यक्ष शामिल रहलें। <ref>{{Cite web|url=http://www.badarikedar.org/content-badari.aspx?id=2|title=Administration of the temple|year=2006|publisher=Shri Badrinath - Shri Kedarnath Temples Committee|archive-url=https://web.archive.org/web/20131029193204/http://www.badarikedar.org/content-badari.aspx?id=2|archive-date=29 October 2013|access-date=1 January 2014}}</ref> बोर्ड में सतरह गो सदस्य बाड़ें; तीन गो के चयन उत्तराखंड विधान सभा द्वारा, एक-एक सदस्य के चयन [[चमोली जिला|चमोली]] पौड़ी [[पौड़ी गढ़वाल जिला|गढ़वाल]], टिहरी [[टिहरी गढ़वाल जिला|गढ़वाल]] आ [[उत्तरकाशी जिला|उत्तरकाशी]] जिला के जिला परिषद द्वारा, आ दस सदस्य उत्तराखंड सरकार द्वारा नामित होखे लें। <ref>{{Cite web|url=http://www.badarikedar.org/content-badari.aspx?id=3|title=Committee members of the temple|year=2006|publisher=Shri Badrinath - Shri Kedarnath Temples Committee|archive-url=https://web.archive.org/web/20131029184513/http://www.badarikedar.org/content-badari.aspx?id=3|archive-date=29 October 2013|access-date=1 January 2014}}</ref> जइसन कि मंदिर के रिकार्ड में संकेत दिहल गइल बा कि मंदिर के पुजारी शिव तपस्वी रहले जेकरा के दंडी संन्यासी कहल जाला, जे आधुनिक [[केरल]] में आम धार्मिक समूह नम्बुदिरी समुदाय से संबंधित रहले। जब 1776 ई. में तपस्वी लोग में से आखिरी आदमी के बिना वारिस के मौत हो गइल त गढ़वाल के राजा केरल से गैर-तपस्वी नम्बुदिरी लोग के पुरोहिताई खातिर बोलवले, ई प्रथा आधुनिक काल में भी जारी बा। <ref>{{Cite web|url=http://www.badarikedar.org/content-badari.aspx?id=4|title=Religious setup of the temple|year=2006|publisher=Shri Badrinath - Shri Kedarnath Temples Committee|archive-url=https://web.archive.org/web/20131029192556/http://www.badarikedar.org/content-badari.aspx?id=4|archive-date=29 October 2013|access-date=1 January 2014}}</ref> 1939 तक मंदिर में भक्त लोग के ओर से दिहल गइल सभ दान रावल (मुख्य पुजारी) के लगे चलत रहे, लेकिन 1939 के बाद उनुकर अधिकार क्षेत्र धार्मिक मामला तक सीमित रह गइल। <ref name="Lochtefeld" /> मंदिर के प्रशासनिक संरचना में राज्य सरकार के आदेश के निष्पादन करे वाला एगो मुख्य कार्यकारी अधिकारी, उप मुख्य कार्यकारी अधिकारी, दू गो ओएसडी, एगो कार्यकारी अधिकारी, एगो खाता अधिकारी, एगो मंदिर अधिकारी, आ एगो सार्वजनिक अधिकारी शामिल होलें जे मुख्य कार्यकारी अधिकारी के सहायता करेलें । <ref>{{Cite web|url=http://www.badarikedar.org/content-badari.aspx?id=6|title=Power structure of the temple|year=2006|publisher=Shri Badrinath - Shri Kedarnath Temples Committee|archive-url=https://web.archive.org/web/20131029184226/http://www.badarikedar.org/content-badari.aspx?id=6|archive-date=29 October 2013|access-date=1 January 2014}}</ref> [[चित्र:Badrinath_Temple_-_OCT_2014.jpg|alt=image showing Badrinath temple with the mountain in the background|thumb|200x200px| गर्मी के दौरान मंदिर के नजारा]] हालांकि बद्रीनाथ उत्तर भारत में स्थित बा, लेकिन मुख्य पुजारी या रावल परंपरागत रूप से दक्षिण भारतीय राज्य [[केरल]] से चुनल नम्बुदिरी ब्राह्मण होखे लें। मानल जाला कि एह परम्परा के शुरुआत आदि शंकर के कइल गइल जे दक्षिण भारतीय दार्शनिक रहलें। रावल के निहोरा उत्तराखंड सरकार केरल सरकार से कइल गइल बा। उम्मीदवार के [[संस्कृत]] में आचार्य (पोस्ट ग्रेजुएट) के डिग्री होखे के चाहीं, स्नातक होखे के चाहीं, ''मंत्र'' (पवित्र ग्रंथ) पाठ में निपुण होखे के चाहीं आ हिंदू धर्म के वैष्णव संप्रदाय के होखे के चाहीं। गढ़वाल के पूर्व शासक जे बद्रीनाथ के संरक्षक प्रमुख बाड़े, केरल सरकार के भेजल उम्मीदवार के मंजूरी दे देले। रावल के स्थापना खातिर तिलक समारोह होला आ जब मंदिर खुलल रहेला त अप्रैल से नवम्बर ले उनुका के प्रतिनियुक्त कइल जाला। रावल के परम पवित्रता के दर्जा (हिज होलीनेस) गढ़वाल राइफल्स अउरी उत्तराखंड के राज्य सरकार देले बिया। नेपाल के राजशाही भी उनका के बहुत सम्मान देला। अप्रैल से नवम्बर ले मंदिर के पुजारी के रूप में आपन कर्तव्य निभावेलें। एकरा बाद या त ज्योतिर्मठ में रहेलें या फिर केरल में अपना पैतृक गाँव वापस आ जालें। रावल के ड्यूटी रोज सबेरे 4 बजे ''अभिषेक'' के साथ शुरू होला। उनुके द्वारा वामन द्वादशी तक नदी ना पार करे के चाहीं आ ''[[ब्रह्मचर्य|ब्रह्माचार्य]]'' के पालन करे के चाहीं। रावल के सहायता चमोली जिला के गाँव डिम्मार से संबंधित गढ़वाली डिमरी ब्राह्मण, नायब रावल, धर्माधिकारी, वेदपाठी, पुजारी के एगो समूह, पंडा समाधि, भंडारी, रसोइया, भक्ति गायक, देवश्रम के चपरासी, जल भरिया ( पानी के रखवाला) आ मंदिर के पहरा देवे वाला लोग मिल के करे ला। बद्रीनाथ उत्तर भारत के कुछ मंदिरन में से एगो ह जवन दक्खिन में अधिका आम श्रौत परंपरा के प्राचीन तंत्र विधि के पालन करेला। <ref>{{Cite web|url=http://traveller.outlookindia.com/fulltravelogue.aspx?id=216|title=Badrinath|last=Outlook Traveller|publisher=Traveller.outlookindia.com|archive-url=https://web.archive.org/web/20131029201558/http://traveller.outlookindia.com/fulltravelogue.aspx?id=216|archive-date=29 October 2013|access-date=1 January 2014}}</ref><ref>{{Cite news|url=http://www.hinduonnet.com/thehindu/fr/2008/07/18/stories/2008071851520400.htm|title=Badrinath Temple|date=18 July 2005|work=[[The Hindu]]|access-date=1 January 2014|archive-url=https://archive.today/20121111030256/http://www.hinduonnet.com/thehindu/fr/2008/07/18/stories/2008071851520400.htm|archive-date=11 November 2012}}</ref> 2012 में मंदिर प्रशासन मंदिर में आवे वाला लोग खातिर टोकन सिस्टम शुरू कइलस। टैक्सी स्टैंड के तीन स्टॉल से दौरा के समय बतावे वाला टोकन दिहल जाला। मुख्य देवता के दर्शन करे खातिर हर भक्त के 10-20 सेकंड के समय दिहल जाला। मंदिर में प्रवेश खातिर पहचान के प्रमाण अनिवार्य बा। <ref>{{Cite web|url=http://www.badarikedar.org/content-badari.aspx?id=29|title=News about the temple|year=2006|publisher=Shri Badrinath - Shri Kedarnath Temples Committee|archive-url=https://web.archive.org/web/20131029195444/http://www.badarikedar.org/content-badari.aspx?id=29|archive-date=29 October 2013|access-date=1 January 2014}}</ref> मंदिर में [[ऋषिकेश]] से पहुँचल जा सकेला, जवन {{Convert|298|km|mi|abbr=on}} पर स्थित बा [[देवप्रयाग]], [[रुद्रप्रयाग]], [[कर्णप्रयाग]], [[नंदप्रयाग]], [[ज्योतिर्मठ]], [[बिष्णुप्रयाग|विष्णुप्रयाग]] आ देवदर्शिनी के रास्ता से दूर बा। [[केदारनाथ मंदिर]] से, आगंतुक {{Convert|243|km|mi|abbr=on|adj=on}} के जात्रा कर सकेलें -लंबा रुद्रप्रयाग मार्ग या {{Convert|230|km|mi|abbr=on|adj=on}} के बा -लंबा उखीमठ अउर गोपेश्वर मार्ग। == इहो देखल जाव == * रामेश्वरम * दिव्य देशम * भारत में धार्मिक पर्यटन == संदर्भ == {{Reflist|33em}} === ग्रंथसूची ===   == आगे पढ़ल जा सकेला == [[श्रेणी:हिंदू तीरथ]] [[श्रेणी:उत्तराखंड]] [[श्रेणी:चार धाम मंदिर]] 8iuqmxia65ybvh27xmljw9ju7um9s0o ओपनएआई 0 88250 803329 766287 2026-07-29T16:21:18Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803329 wikitext text/x-wiki {{Infobox company|name=OpenAI|logo=OpenAI Logo.svg|caption=|website={{Official URL}}}} '''ओपनएआई''' एगो आर्टिफिशियल इंटेलिजेंस (AI) रिसर्च लैबोरेटरी हवे जेह में लाभ खातिर निगम '''ओपनएआई एलपी''' आ एकर मूल कंपनी, गैर-लाभकारी '''ओपनएआई इंक शामिल बाड़ी सऽ।''' कंपनी एआई के क्षेत्र में शोध करेले, जवना के बतावल लक्ष्य बा कि दोस्ताना एआई के बढ़ावा दिहल अउरी विकसित कइल जाए, जवना से समग्र रूप से मानवता के फायदा होखे। एह संगठन के स्थापना 2015 के अंत में सैन फ्रांसिस्को में सैम आल्टमैन, [[एलन मस्क]], आ अउरी लोग द्वारा कइल गइल, जे लोग सामूहिक रूप से 1&nbsp;अरब अमेरिकी डॉलर के वादा कइल। मस्क फरवरी 2018 में बोर्ड से इस्तीफा दे दिहलें बाकी दानकर्ता बनल रहलें। 2019 में ओपनएआई एलपी के [[माइक्रोसाफ्ट|माइक्रोसॉफ्ट]] आ मैथ्यू ब्राउन कंपनी के द्वारा 1&nbsp; अरब {{US$}} के निवेश भइल। ओपनएआई के मुख्यालय मिशन डिस्ट्रिक्ट, सैन फ्रांसिस्को में पायनियर बिल्डिंग में बा। ==इतिहास== दिसंबर 2015 में, सैम आल्टमैन, एलन मस्क, ग्रेग ब्रॉकमैन, रीड हॉफमैन, जेसिका लिविंगस्टन, पीटर थिल, अमेजन वेब सर्विसेज (AWS), इन्फोसिस, आ वाईसी रिसर्च ओपनएआई के गठन के घोषणा कइलें<ref>{{Cite web |date=2015-12-12 |title=Introducing OpenAI |url=https://openai.com/blog/introducing-openai/ |access-date=2022-12-23 |website=OpenAI |language=en}}</ref> आ {{USD}}1&nbsp;अरब के खर्चा के वादा उठवलें लोग। संगठन के कहनाम रहे कि ऊ आपन पेटेंट आ रिसर्च के जनता खातिर खुला बना के अन्य संस्थान आ शोधकर्ता लोग के साथे "स्वतंत्र रूप से सहयोग" करी।<ref>{{cite web |title=Introducing OpenAI |url=https://blog.openai.com/introducing-openai/ |website=OpenAI Blog |date=December 12, 2015 |access-date=January 6, 2023 |archive-date=February 24, 2019 |archive-url=https://web.archive.org/web/20190224031626/https://blog.openai.com/introducing-openai/ |url-status=dead }}</ref> ओपनएआई के मुख्यालय सैन फ्रांसिस्को के मिशन डिस्ट्रिक्ट में पायनियर बिल्डिंग में बा।<ref name=":02">{{Cite news|url=https://gizmodo.com/elon-musks-neuralink-sought-to-open-an-animal-testing-f-1823167674|title=Elon Musk's Neuralink Sought to Open an Animal Testing Facility in San Francisco|last=Conger|first=Kate|work=Gizmodo|access-date=October 11, 2018|language=en-US}}</ref><ref name=technologyreview>{{Cite web|url=https://www.technologyreview.com/s/615181/ai-openai-moonshot-elon-musk-sam-altman-greg-brockman-messy-secretive-reality/|title=The messy, secretive reality behind OpenAI's bid to save the world|last=Hao|first=Karen|date=February 17, 2020|website=MIT Technology Review|language=en-US|access-date=March 9, 2020}}</ref> {{clear}} == संदर्भ == {{Reflist|33em}} [[श्रेणी:कंपनी]] {{Company-stub}} eb59rvg3f4185bqtjlz8zl0iz8i9ncy भानुभक्त आचार्य 0 88343 803370 772055 2026-07-29T16:24:22Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803370 wikitext text/x-wiki {{Infobox person | honorific_prefix = '''आदिकबि''' | name = भानुभक्त आचार्य | image = File:Bhanubhakta Acharya.jpg | alt = नेपाली कबी के चित्र | caption = कबी के एगो पोर्ट्रेट | birth_date = {{Birth date|df=yes|1814|7|13}} | birth_place = Chundi Ramgha, [[Tanahu District]], [[Kingdom of Nepal]]<br><small>({{Present-day|[[Nepal]]}})</small> | death_date = {{Death year and age|1868|1814}} | death_place = Setighat, [[Tanahun District|Tanahun, Kingdom of Nepal]]<br> <small>({{Present-day|Nepal}})</small> | nationality = नेपाली | occupation = [[कबितई|कबी]] | era = भानुभक्त जुग | notable_works = ''भानुभक्त रामायण'', ''घांसी'' | parents = {{Plainlist| *धनंजय आचार्य (बाबूजी) *धर्मावती आचार्य (महतारी)}} }} '''भानुभक्त आचार्य''' (1814—1868 ई.) (1871—1925 [[विक्रम संवत|बिसं]] ) नेपाली साहित्यकार, कवि आ अनुवादक रहलें। इनके [[नेपाली भाषा]] के पहिला कवि के रूप में ब्यापक रूप से मानल जाला, जेकरा खातिर इनके "आदिकबी" के उपाधि दिहल गइल बाटे। महाकाव्य ''[[रामायण]]'' के पहिला बेर [[संस्कृत]] से नेपाली में अनुवाद करे खातिर इनके जानल जाला। शुरू में उनकर रामायण के अनुवाद मुँहजबानी रूप में लोकप्रिय भइल। बाद में एकर संकलन आ प्रकाशन मोतीराम भट्ट द्वारा 19वीं सदी के अंत में ''[[भानुभक्त रामायण]]'' के रूप में कइल गइल। == शुरुआती जिनगी आ शिक्षा == भानुभक्त आचार्य के जनम 13 जुलाई 1814 (29 आषाढ़ 1871 ईसा पूर्व) के नेपाल के [[विक्रम संवत|तनहुन]] [[तनहुँ जिला|जिला]] के चुंडी रामघा गाँव में धनंजय आचार्य, आ धर्मवती आचार्य के घरे भइल रहे।<ref>{{Cite web|url=https://english.onlinekhabar.com/bhanu-bhakta-acharya-nepal-aadikavi.html|title=Bhanu Bhakta Acharya: 5 reasons why a poet became a household name in Nepal|date=13 July 2021|website=OnlineKhabar|language=en-GB|archive-url=https://web.archive.org/web/20210713043312/https://english.onlinekhabar.com/bhanu-bhakta-acharya-nepal-aadikavi.html|archive-date=13 July 2021|access-date=2021-07-20}}</ref> उनकर पिता धनंजय आचार्य सरकारी अधिकारी रहले अउरी सभ भाई में सबसे बड़ रहले। भानुभक्त के प्राथमिक शिक्षा [[संस्कृत]] के अपना घर में दादाजी से आ बाद में [[बनारस|वाराणसी]] में भइल।<ref name=":0">{{Cite web|url=https://annapurnapost.com/news/kin-smjhne-bhaanulaaii-161113|title=किन सम्झने भानुलाई|last=आचार्य|first=काशीराज|date=2020-07-13|website=[[The Annapurna Express|The Annapurna Post]]|language=ne|access-date=2022-07-28}}</ref><ref name="boss">{{Cite web|url=http://bossnepal.com/adikabi-bhanubhakta-acharya|title=Adikabi Bhanubhakta Acharya|last=<!--Staff writer(s); no by-line.-->|publisher=Boss Nepal|location=Kathmandu|language=en|archive-url=https://web.archive.org/web/20190222151947/http://bossnepal.com/adikabi-bhanubhakta-acharya/|archive-date=22 February 2019|access-date=22 February 2019}}</ref> == साहित्यिक कैरियर == नेपाली भाषा सहित दक्खिन एशियाई भाषा सभ ओह समय में अधिकतर भाषा प्रसार के मौखिक माध्यम ले सीमित रहलीं जिनहन के लिखित संदर्भ आ साहित्यिक परभाव बहुत कम रहे। चूंकि [[दक्खिन एशिया]] के अधिकतर लिखित ग्रंथन में संस्कृत के बोलबाला रहे एहसे ई अधिकतर आम जनता खातिर दुर्गम रहे। चूंकि ब्राह्मण लोग गुरु, विद्वान आ पुरोहित के रूप में उत्कृष्टता हासिल करे वाला जाति रहे एहसे धार्मिक शास्त्र आ अउरी साहित्यिक रचना सभ के पहुँच खाली ओह लोग तक सीमित रहे आ कुछे लोग अइसनो रहे जे शिक्षा पा सकत रहे आ संस्कृत के भी समझ सकत रहे। कई गो कवि लोग संस्कृत में कविता लिखले रहे जबकि आचार्य नेपाली भाषा में लिखे लगले जवना से खाली भर भाषा के लोकप्रियता ना मिलल बलुक उनुके राणा शासक लोग के भी स्वीकृति मिलल। राम के वीर कारनामा के प्रति आचार्य के परोपकार उनका में आपन कथा के नेपाली बोले वाला लोग तक सुलभ बनावे के तात्कालिकता लेके आइल। चूँकि अधिकतर लोग संस्कृत भाषा ना समुझत रहे एहसे ऊ महाकाव्य के नेपाली भाषा में अनुवाद कइलें। ''रामायण'' के गीतात्मक कथ्य शैली के संरक्षित करत उनकर अनुवाद विद्वान लोग के मानना बा कि उहे गीतात्मक सार "भाव आ ''मरम" के वाहक बा जवन कविता नियन ना लागे के बजाय क्षेत्रीय प्रभाव के विकृत कइले बिना गीत के रूप में जादा सुनाई देलस'' रामायण ''के भीतरी अर्थ के बिना बिगड़ले।<ref name=":0" /> उहाँ के कवनो पाश्चात्य शिक्षा ना मिलल आ ना ही विदेशी साहित्य से परिचित रहले जवना से उहाँ के रचना आ अनुभव यात्रा के लोकभाषा साहित्यिक व्यवस्था के मौलिक बना के रखलस आ उहाँ के रचना में मजबूत नेपाली सवाद ले आइल। उनकर लेखन के प्रमुख विशेषता सरल रहे बाकिर मजबूत रहे जवना में धर्म के भाव, सादगी के भाव आ देश के गर्मजोशी रहे जवना के तुलना अउरी कई गो कवि ना कर पवले रहल। एगो अमीर परिवार के होखला के नाते उनुका कबो कवनो आर्थिक परेशानी ना भइल अउरी उनुकर जीवन तब तक अनोखा रहे, जब तक कि उनुकर मुलाकात एगो घास काटे वाला से ना भइल, जवन कि समाज के कुछ देवे के चाहत रहे ताकि उनुका के मरला के बाद भी याद कइल जा सके। घास काटे वाला के बात ही उनका के अइसन काम करे के प्रेरणा दिहलस जवन समाज पर आपन छाप छोड़ें। उ अपना जीवन में दुगो कृति लिखले जवना में से एगो ''भानुभक्तेय रामायण'' अउरी दूसरा जेल में रहत छंद के रूप में लिखल प्रधानमंत्री के चिट्ठी ह।<ref>{{Cite web|url=https://beta.gorkhapatraonline.com/featured/2021-07-10-41602|title=भानुभक्तीय रामायण : दृष्टिविहीनलाई पनि|last=आचार्य|first=जयराज|date=2021-07-10|website=[[Gorkhapatra]]|language=ne|access-date=2022-07-28|archive-date=2022-07-28|archive-url=https://web.archive.org/web/20220728181652/https://beta.gorkhapatraonline.com/featured/2021-07-10-41602|url-status=dead}}</ref> कागज प हस्ताक्षर करे में कुछ गलतफहमी के चलते उनुका के बलि के बकरा बना के जेल भेज दिहल गइल। जेल में उनकर तबियत बिगड़ गइल आ उनका के मुक्त होखे के झूठा उम्मीद दिहल गइल बाकिर उनकर केस के सुनवाई तक ना भइल। त, उ प्रधानमंत्री के एगो याचिका लिख के आपन आजादी के निहोरा कइले, जवन बाद में उनुकर एगो महान रचना बन गइल। उनुका कविता से ना खाली आजादी जीतल गइल बलुक पइसा के झोरा भी दिहल गइल (उहाँ के ओही भाषा में लिखले रहले जवना के तब के प्रधानमंत्री जनता के जबरन इस्तेमाल करे के चाहत रहलें)। 1868 में जब उनकर निधन भइल त उनका मालूम ना रहे कि ऊ एक दिन नेपाल के सबसे पूज्य कवि में से एक हो जइहें। बाकिर उनकर रचना प्रकाशित ना भइल आ उनकर योगदान के श्रेय बिना मिलले उनकर निधन हो गइल। उनकर रचना मोतीराम भट्ट द्वारा 1887 में प्रकाशित भइल जब ऊ पाण्डुलिपि के खोज के भारत के [[बनारस]], छपाई खातिर ले गइलें। आचार्य के एगो रचना काठमांडू घाटी आ ओकरा निवासी लोग के रंगीन, चमकदार प्रशंसा खातिर जानल जाला। भले ऊ नेपाल के सबसे मशहूर आ पूज्य कवि लोग में से एक हउवें बाकिर नेपाली साहित्य के इतिहास में उहाँ के रचना ओतना परसिद्ध नइखे जतना कि आन कवि लोग। रामायण, जेकर मूल रूप से आचार्य नेपाली में अनुवाद कइले रहलें, एक सदी से ढेर समय बाद अंग्रेजी में अनुवाद भइल; एकरा के अंग्रेजी के पहिला अनुवाद मानल जाला जवन नेपाल में प्रकाशित भइल।<ref>{{Cite web|url=https://risingnepaldaily.com/news/13638|title=A Nepali Ramayana For English Readers|last=Adhikary|first=Dhruba H.|date=2022-07-08|website=[[The Rising Nepal]]|publisher=[[Gorkhapatra Corporation]]|archive-url=https://web.archive.org/web/20220709163757/https://risingnepaldaily.com/news/13638|archive-date=2022-07-09|access-date=2022-07-29}}</ref> == उल्लेखनीय रचना == === महाकाव्य === * ''भानुभक्त रामायण'' === कविता === * ''अमरावती कान्तिपुरी नगरी'' * ''गांसी <ref>{{Cite web|url=https://www.adb.org/sites/default/files/project-documents/52097/52097-002-iee-en.pdf|title=NEP: SASEC Mugling–Pokhara Highway Improvement Phase 1 Project|website=Asian Development Bank|page=44|archive-url=https://web.archive.org/web/20200720131153/https://www.adb.org/sites/default/files/project-documents/52097/52097-002-iee-en.pdf|archive-date=20 July 2020|access-date=20 July 2020}}</ref>'' * ''बढ़ू शिक्षा'' * ''भक्त माला'' * ''खूमित हाँ गिरधारी ले'' * ''रोज रोज दर्शन पौंछू (भोली कबीता)'' * ''मा भानुभक्त'' * ''बालाजी देखियन'' * ''प्रश्नोत्तर माला'' [[चित्र:Bhanu_Bhakta_Acharya_(1814-1869)_author.jpg|alt=Post stamp issued in Acharya's honour|thumb|319x319px| आचार्य के सम्मान में जारी डाक टिकट]] == विरासत == भानुभक्त आचार्य के नेपाली भाषा के ''आदिकबी'' (पहिल कवि) के उपाधि से पूज्य आ सम्मानित कइल जाला। मोतीराम भट्ट, पहिला बेर 1981 में आचार्य के जीवनी लिखत घरी उनुका के आदिकवी के नाम से संबोधित कइले रहलें। ऊ साफ करत बाड़न कि आचार्य के ''आदिकवि'' ना कहल जाला काहे कि ऊ नेपाल के पहिला कवि रहले बाकिर ऊ उपाधि के हकदार रहले काहे कि ऊ पहिला कवि रहले जे कविता के ''मरम'' (अंतर्सार) के समझ के लिखले रहले। <ref name="boss" /><ref name="OMN">{{Cite web|url=http://english.ohmynews.com/articleview/article_view.asp?no=304833&rel_no=1|title=Bhanubhakta: The First Poet Of Nepali language|last=Bishnu K.C.|date=14 July 2006|publisher=Oh My Newsl|language=en|archive-url=https://web.archive.org/web/20191106025354/http://english.ohmynews.com/articleview/article_view.asp?no=304833&rel_no=1|archive-date=6 November 2019|access-date=22 February 2019}}</ref> '''भानु जयंती''' भानुभक्त आचार्य के जयंती के उत्सव ह। नेपाली कैलेंडर के अनुसार ई आषाढ़ महीना के 29 तारीख के पड़ेला। एकरा के हर साल नेपाल सरकार आ नेपाली लोग के साथे-साथे दुनिया भर के [[नेपाली भाषा|नेपाली]] भाषी लोग द्वारा मनावल जाला। सांस्कृतिक उत्सव, '''भानुभक्त आचार्य के''' जन्मदिन के याद में दुनिया भर के [[नेपाल|नेपाली]] लोग के बीच प्रचलित बाटे। <ref>{{Cite web|url=https://morungexpress.com/why-do-gorkhas-celebrate-bhanu-jayanti-as-their-cultural-festival|title=Why do Gorkhas celebrate Bhanu Jayanti as their Cultural Festival?|last=Sundas|first=Jacob|date=2022-07-07|website=[[The Morung Express]]|archive-url=https://web.archive.org/web/20220709190808/https://morungexpress.com/why-do-gorkhas-celebrate-bhanu-jayanti-as-their-cultural-festival|archive-date=2022-07-09|access-date=2022-07-09}}</ref> आमतौर पर ई 13 जुलाई या नेपाली आषाढ़ महीना के 29 तारीख के मनावल जाला। हर साल भानु जयंती साहित्यिक गोष्ठी, आ कार्यक्रम के साथे आ नेपाली लेखक, उपन्यासकार, आ अउरी साहित्यिक हस्ती/उत्साही लोग के उल्लेखनीय उपस्थिति के बीच एगो मेगा इवेंट के रूप में मनावल जाला। <ref name="boss" /><ref>{{Cite news|url=https://thehimalayantimes.com/kathmandu/pm-oli-urges-new-generation-to-follow-ideals-of-bhanubhakta|title=PM Oli urges new generation to follow ideals of Bhanubhakta|last=<!--Staff writer(s); no by-line.-->|date=13 July 2018|access-date=21 February 2019|archive-url=https://web.archive.org/web/20190222041943/https://thehimalayantimes.com/kathmandu/pm-oli-urges-new-generation-to-follow-ideals-of-bhanubhakta/|archive-date=22 February 2019|location=Kathmandu|language=en}}</ref><ref>{{Cite news|url=https://kathmandupost.ekantipur.com/news/2018-07-13/morning-procession-to-mark-bhanu-jayanti-in-photos.html|title=Morning procession to mark Bhanu Jayanti|last=<!--Staff writer(s); no by-line.-->|date=13 July 2018|access-date=21 February 2019|archive-url=https://web.archive.org/web/20190221224020/https://kathmandupost.ekantipur.com/news/2018-07-13/morning-procession-to-mark-bhanu-jayanti-in-photos.html|archive-date=21 February 2019|location=Kathmandu|language=en}}</ref> == गैलरी == <gallery> चित्र:Statue of Bhanubhakta Acharya at Chundi Ramgha 01.jpg|चुंडी रामघा में भानुभक्त आचार्य की प्रतिमा चित्र:Bhanu salik Nepal Academy5.JPG|[[Nepal Academy|नेपाल अकादमी]] में भानुभक्त की प्रतिमा चित्र:Aadikavi Bhanubhakta Acharya.jpg|भानुभक्त आचार्य के बस्ट चित्र:Bhanubhakta letter.jpg|भानुभक्त आचार्य के पुत्र के पत्र (1858) चित्र:Bhanubhakta statue.JPG|[[Chowrasta (Darjeeling)|चौरास्ता]], [[Darjeeling|दार्जिलिंग]] में भानुभक्त आचार्य की प्रतिमा चित्र:Bhanu Ghansi Memorial ParkTanahu Ward No. 12 Byas Municipility Gandaki Pradesh Nepal Rajesh Dhungana (28).jpg|alt=भानु घंसी मेमोरियल पार्क, तनहुन में भानुभक्त का बस्ट|भानु घंसी मेमोरियल पार्क, [[Tanahun District|तनहुन]] में भानुभक्त का बस्ट </gallery> == इहो देखल जाव == * गांसी, जे भानुभक्त आचार्य के संस्कृत महाकाव्य ''रामायण'' के अनुवाद करे खातिर प्रेरित कइले रहले * शक्ति बल्लव अर्यल == संदर्भ == {{Reflist|33em}} [[श्रेणी:नेपाली-भाषा के कबी]] [[श्रेणी:हिंदू कबी]] k841dmd07ayjxexxxkwnhoz5lgzfogg लाल बहादुर शास्‍त्री राष्‍ट्रीय प्रशासनिक प्रशिक्षणदेयी संस्था 0 88384 803355 784047 2026-07-29T16:23:09Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803355 wikitext text/x-wiki {{Cleanup|reason=फ़ॉर्मेटिंग में कई सारा समस्या, फालतू बोल्ड पाठ वगैरह बाटे}} {{Infobox university |name=Lal Bahadur Shastri National Academy of Administration |native_name= |native_name_lang= |image=Lbsnaa_academy_logo.jpg |image_size=150px |former_name=राष्ट्रीय प्रशासनिक संस्था |motto= "शीलं परं भूषणम्" ([[संस्कृत| संस्कृतम्]])<br> "चरीत्र सभसे बड़ गहना बा" |motto_lang=san |motto in bh="चरित्र से बड़का गहना कुछो नइखे" |established=1958 |type=[[लोक सेवा]] प्रशिक्षण संस्थान |director=श्रीनिवास कटीकीतला, [[भारतीय प्रशासनिक सेवा|IAS]] |city=[[शिमला]] आ [[न्यू दिल्ली]] 1972 में; [[मसूरी]], [[उत्तराखण्ड]] वर्तमान में |country=भारत |campus=शहरी |website={{URL|http://www.lbsnaa.gov.in}} |logo= |parent=[[जनोरहन आ पेंशन मंत्रालय]] }} '''लाल बहादुर शास्त्री राष्ट्रीय प्रशासनिक प्रशिक्षणदेयी संस्था''' [[भारत|भारतऽगणराज्य]] में [[लोकनीति]] आ [[लोक प्रशासन]] पर सिविल सेवा प्रशिक्षण देवनीहार संस्थान बा।<ref>{{cite web |url=https://byjus.com/free-ias-prep/lbsnaa-history-and-some-interesting-facts/}}</ref>  संस्था के मुख्य मकसद आई.ए.एस सेवादेयी सदस्यन के प्रशिक्षित कइल आ ग्रुप-ए केन्द्रीय सिविल सेवा के फाउंडेशन कोर्स के संचालनो करऽल बा।  प्रशिक्षण पूरा होखला के बाद आई.ए.एस सेवादेयी सदस्यन के प्रशिक्षु अधिकारीयन के [[जवाहरलाल नेहरू विश्वविद्यालय]], [[दिल्ली]] से ए.मए (लोक प्रशासन) से सम्मानित कइल जाला।  ई 1985 से [[जनोरहन आ पेंशन मंत्रालय]] के तहत काम करत बा। एल.बी.एस.एन.ए.ए.<ref>{{cite web |url=https://www.eliteias.in/everything-about-lbsnaa/}}</ref> [[उत्तराखंड|उत्तराखण्ड]] के [[मसूरी]] के सुन्दरं पर्वत में स्थित एगो प्रमुख प्रशिक्षणदेयी संस्था बा।1958 में स्थापित भइल एह प्रशिक्षणदेयी संस्था में हर बरिस सिविल सेवकन के एगो नया बेच लौकेला। प्रशिक्षणदेयी संस्था [[अखिल भारतीय सेवा]] में प्रवेश कर्नीहार लोगीन खातिर एगो सामान्य फाउंडेशन कोर्स चलावेले। अक्टूबर 1972 में प्रशिक्षणदेयी संस्था के नांव बदल के '''"लाल बहादुर शास्त्री अकादमी ऑफ एडमिनिस्ट्रेशन"'''<ref>{{cite web|url=https://iasbabuji.com/upsc-training/lbsnaa/|title=आर्काइव कॉपी के बा|access-date=2023-01-22|archive-date=2023-01-22|archive-url=https://web.archive.org/web/20230122053458/https://iasbabuji.com/upsc-training/lbsnaa/|url-status=dead}}</ref> कर दिहल गइल आ जुलाई 1973 में एहमें राष्ट्रीय शब्द जोड़ल गइल। अब प्रशिक्षण संस्था के "'''लाल बहादुर शास्त्री नेशनल एकेडमी ऑफ एडमिनिस्ट्रेशन" (LBSNAA)''' के नांव से चिन्हल जाला। जेकरा के [[भोजपुरी]] मे '''"लाल बहादुर शास्त्री राष्ट्रीय प्रशासनिक प्रशिक्षणदेयी संस्था"''' कहल जाला। अप्रिल 1985 से ई जनोरहन आ पेंशन मंत्रालय के तहत काम करत बा। == परिचय == [[चित्र:Lal_Bahadur_Shastri_National_Academy_of_Administration,_Mussoorie,_at_night.JPG|alt=Lal Bahadur Shastri National Academy of Administration, Mussoorie, at night|thumb|लाल बहादुर शास्त्री राष्ट्रीय प्रशासनिक प्रशिक्षणदेयी संस्था, राती में]] 15 अप्रिल 1958 के तत्कालीन केंद्रीय गृहमंत्री [[पण्डित गोविन्द बल्लभ पन्त]] [[लोकसभा]] में घोषणा कइले के [[भारत सरकार|सरकार]] एगो नेशनल एकेडमी ऑफ एडमिनिस्ट्रेशन के स्थापना करी, जहवां सिविल सेवा में भर्ती होखनिहारन के प्रशिक्षण दिहल जाई। [[घर मंत्रालय, भारत|गृह मंत्रालय]] दिल्ली के आई.ए.एस ट्रेनिंग स्कूल आ [[शिमला]] के आई.ए.एस स्टाफ कॉलेज के मिला के 1 सितंबर 1959 में [[मसूरी]] में नेशनल एकेडमी ऑफ एडमिनिस्ट्रेशन के स्थापित करऽल गइल। प्रशिक्षणदेयी संस्था दु बेरा आपन नांव में बदलाव कइलस। अक्टूबर 1972 में एकर नाम बदल के '''"लाल बहादुर शास्त्री एकेडमी ऑफ एडमिनिस्ट्रेशन"''' रखल गइल। जुलाई 1973 में एह संस्था के वर्तमान नाँव '''"लाल बहादुर शास्त्री नेशनल एकेडमी ऑफ एडमिनिस्ट्रेशन (LBSNAA)"''' धरल गइल। अधिकारीयन के पहिला बॅच के प्रशिक्षण 13 अप्रैल 1959 में 115 अधिकारीयन के सङे मेटकाफ हाउस, दिल्ली में शुरू भइल। 1 सितंबर 1959 में प्रशिक्षणदेयी संस्था मसूरी के चार्लेविले होटल में स्थानफेर हो गइल आ वर्तमान में एहिजा स्थिर बा। मसूरी में प्रशिक्षणदेयी संस्था द्वारा शुरू कइल गइल चार्लेविले होटल एह [[हिल स्टेशन]] में बनल पहिला होटल रहल। एकरा के सरकार निजी व्यक्ति से किन्ले रहे। एह होटल के मुख्य भवन 1854 में जनरल विल्किंसन द्वारा बनवावल गइल रहे। एकरा के 1861 में मसूरी धननिधिकोष (बैंक) के रिटायर्ड मैनेजर मिस्टर हॉब्सन किन्ले रहले। मशहूर लेखक [[रडयार्ड किपलिंग|रुडयार्ड किपलिंग]] 1888 में इहावा रहऽत रहले। 1905 में यूके के रानी मैरी (ओह घरी प्रिंसेस ऑफ वेल्स के नाम से जानल जाली) एह होटल में रुकल रहली। 31 अगस्त 1970 के गृह मंत्रालय के तहत प्रशिक्षणदेयी संस्था के कामकाज शुरू भइल। 1 सितंबर 1970 से अप्रैल 1977 ले ई संस्था [[कैबिनेट सचिव मामिला मंत्रालय]] के नियंत्रण में रहल, अप्रैल 1977 से मार्च 1985 ले फेनु से [[घर मंत्रालय, भारत|गृह मंत्रालय]] के नियंत्रण में आगई आ अप्रैल 1985 से आजले [[जनोरहन आ पेंशन मंत्रालय]] के नियंत्रण मे काम करता। 1984 में एगो भयानक आग लगला से मुख्य होटल के भवन तबाह हो गइल रहे। एन.आई.सी.टी.यू (NICTU) के स्थापना 1988 में भइल रहे। 1989 में "नेशनल सोसाइटी फॉर प्रोमोशन ऑफ डेवलपमेंट एडमिनिस्ट्रेशन रिसर्च एंड ट्रेनिंग (NSDART)" के स्थापना भइल। 14 नवंबर 1996 से एकरा के "नेशनल इंस्टीट्यूट ऑफ एडमिनिस्ट्रेशन रिसर्च" (NIAR)के नाम से जानल जाला। 3 नवंबर 1992 में प्रशिक्षणदेयी संस्था के कर्मशिला भवन आ 9 अगस्त 1996 ध्रुवशिला आ कालिंदी अतिथि गृह के उद्घाटन करऽल गइल।2011 में भवन ज्ञानशिला, सिल्वरवुड आ वैली व्यू के निर्माण भइल। 21 जून 2013 ब्रह्मपुत्र भवन आ 29 जून 2015 आधारशिला भवन के उद्घाटन भइल। [[चित्र:The_Prime_Minister,_Shri_Narendra_Modi_in_a_group_photograph_with_the_Officer_Trainees_of_the_92nd_Foundation_Course,_at_the_Lal_Bahadur_Shastri_National_Academy_of_Administration_(LBSNAA),_in_Mussoorie,_Uttarakhand_(1).jpg|alt=The Prime Minister, Shri Narendra Modi in a group photograph with the Officer Trainees of the 92nd Foundation Course, at the Lal Bahadur Shastri National Academy of Administration (LBSNAA), in Mussoorie, Uttarakhand (1)|thumb|संस्था के एगो कार्यक्रम में प्रधानमंत्री मोदी]] भारत मे देश के प्रमुख सिविल सेवा में ढेरका अधिकारीयन के पसंदगी [[संघ लोक सेवा आयोग]] (UPSC) के ओर से आयोजित सिविल सेवा परीक्षा के माध्यम से होखेला। पसंद कएल उम्मीदवार चार महीना के फाउंडेशन कोर्स खातिर लाल बहादुर शास्त्री राष्ट्रीय प्रशासनिक प्रशिक्षणदेयी संस्था में शामिल होलें आ एह कोर्स में सगरी प्रशिक्षु लोग में "समानता" के भाव पन्कावल जाला. एकरा बाद [[भारतीय प्रशासनिक सेवा]] (IAS)के अधिकारी अकादमी में आपन व्यवसायिक अभ्यास प्रशिक्षण जारी रखेले,<ref name="byjus.com">{{cite web |url=https://byjus.com/free-ias-prep/lbsnaa-ias-training-bharat-darshan/ |website=byjus}}</ref> जबके दोसर सेवा के अधिकारी, माने [[भारतीय विदेश सेवा]] (IFS) के अधिकारी खातीर न्यू दिल्ली के "[[सुषमा स्वराज विदेश सेवा संस्थान]]" आ [[भारतीय पुलिस सेवा]] (IPS) के अधिकारीयन खातिर [[हैदराबाद]] में [[सरदार वल्लभभाई पटेल राष्ट्रीय पुलिस प्रशिक्षणदेयी संस्था]], आ [[भारतीय वन सेवा]] (IFS) के अधिकारी खातिर [[इंदिरा गांधी राष्ट्रीय वन प्रशिक्षणदेयी संस्था]], [[देहरादून]] आ भारतीय राजस्व सेवा (IRS) के अधिकारीयन खातिर "नेशनल एकेडमी ऑफ कस्टम इन्डायरेक्ट टैक्स एंड नारकोटिक्स" बाटे। एकरा बाद [[भारतीय प्रशासनिक सेवा]] (IAS)के अधिकारी अकादमी में आपन व्यवसायिक अभ्यास प्रशिक्षण जारी रखेले,<ref name="byjus.com"/> जबके दोसर सेवा के अधिकारी, माने [[भारतीय विदेश सेवा]] (IFS) के अधिकारी खातीर न्यू दिल्ली के "[[सुषमा स्वराज विदेश सेवा संस्थान]]" आ [[भारतीय पुलिस सेवा]] (IPS) के अधिकारीयन खातिर [[हैदराबाद]] में [[सरदार वल्लभभाई पटेल राष्ट्रीय पुलिस प्रशिक्षणदेयी संस्था]], आ [[भारतीय वन सेवा]] (IFS) के अधिकारी खातिर [[इंदिरा गांधी राष्ट्रीय वन प्रशिक्षणदेयी संस्था]], [[देहरादून]] आ भारतीय राजस्व सेवा (IRS) के अधिकारीयन खातिर "नेशनल एकेडमी ऑफ कस्टम इन्डायरेक्ट टैक्स एंड नारकोटिक्स" बाटे। प्रशिक्षणदेयी संस्था 2007 में भारतीय प्रशासनिक सेवा के अधिकारीयन खातिर मिड कैरियर ट्रेनिंग प्रोग्रामो चलावल शुरू कइलस। अंदाजे 15 साल के सेवा वाला अधिकारी जे [[संयुक्त सचिव]] बने वाला बाड़ें, चौथा चरण मिड कैरियर ट्रेनिंग प्रोग्राम से गुजरेलें, जबके लगभग आठ साल के सेवा वाला अधिकारी सेवा के तृतीय चरण के मध्य कैरियर प्रशिक्षण कार्यक्रम से गुजरेले। प्रशिक्षण खातिर सबसे व्यापक ज्ञान के आधार उपलब्ध करावे खातिर अकादमी दुनिया के मशहूर थिंक टैंक आ आइवी लीग रिसर्च इंस्टीट्यूशन के जवड़े काम कर रहल बा। [[गूगल]] आ सिद्धार्थ पॉल तिवारी के सङे साझेदारी के साथे प्रशिक्षणदेयी संस्था नवीनतम् तकनीक के प्रशिक्षण शुरू कइलक एह से प्रशिक्षुवन एह क्षेत्र में भइल सभसे ताजा नवाचार सभ के बारे में जानकारी ले सकेले। प्रशिक्षणदेयी संस्था शासन आ लोक प्रशासन के बिबिध पासा में केतनहे नन्हिचुकी अवधि के प्रशिक्षण कार्यक्रमो चलावे ले। ==सुविधा== प्रशिक्षणदेयी संस्था में सेवादेयी सदस्यन के विशाल आ सुसज्जित लेक्चर हॉल, ग्रन्थालय, सभाखण्ड, ऑडिटोरियम, मेस, निवासस्थान, खेल परिसर, मेडिकल सुविधा, रिप्रोग्राफिक यूनिट, ई.पी.ए.बी.एक्स (EPABX), [[कंप्यूटर]] लॅब, बैंक, डाकघर आ [[इंटरनेट]] के सुविधा मिली। === शिक्षण सुविधा === प्रशिक्षणदेयी संस्था में नवीनतम् ऑडियो-विजुअल सुविधा वाला विशाल आ सुसज्जित कक्षा के साथे-साथे वीडियो-रिकॉर्डिंग आ वीडियो-कांफ्रेंसिंग के सुविधा उपलब्ध बा। प्रशिक्षणदेयी संस्था में चारगो मुख्य भवन बा; '''कर्मशिला''', '''ध्रुवशिला''', '''ज्ञानशिला''' आ '''आधारशिला'''। कर्मशिला में लेक्चर हॉल, अधिकारीयन के मेस, ग्रन्थालय आ अधिकारीयन आरामस्थानो बा। फेकल्टी कार्यालय आ प्रशासनिक कार्यालय ध्रुवशिला आ ज्ञानशिला में बा। ज्ञानशिला में तनी-बहुत कक्षो बा। आधारशिला में कंप्यूटर लॅब आ तनी-बहुत कक्षो बा। === निवासस्थान सुविधा === प्रशिक्षण के बेरीया अधिकारी प्रशिक्षुवन के प्रशिक्षणदेयी संस्था में रहे के मनसा करल जाला। प्रशिक्षुवन खातिर तीनगो हॉस्टल बाटे जेकर के नाम [[भारत]] के तीनगो बड़ [[नदी|नद्दीयन]] के नाम पर धरल गइल बाटे [[गंगा नदी|गंगा]], [[कावेरी नदी|कावेरी]] आ [[नर्मदा नदी|नर्मदा]]। प्रशिक्षणदेयी संस्था हॉस्टल के सभे कमरा में [[केंद्रीय तापन-प्रणाली]], [[टेलीविजन]], [[टेलीफोन]] बा आ डबल ओक्यूपेंसी के सुविधा बा। मिड कैरियर ट्रेनिंग प्रोग्राम के प्रतिभागियन के सिल्वरवुड, हेप्पी वैली, [[महानदी]] आ वैली व्यू एक्जीक्यूटिव हॉस्टल में राखल गइल बा। सभे हॉस्टल में आरामस्थान, ऑडियो-विजुअल उपकरण, आधुनिक लॉन्ड्रोमैट आ आम [[मनोरंजन]] क्षेत्र बा। विजिटिंग फैकल्टी कालिंदी अतिथि गृह में बाटे। प्रशिक्षणदेयी संस्था के ब्रह्मपुत्र भवन में साङ्घतीया आ सहायक फैकल्टी खातिर स्टूडियो अपार्टमेंट बाटे। === खेला परिसर === लाल बहादुर शास्त्री राष्ट्रीय प्रशासनिक प्रशिक्षणदेयी संस्था में एगो अद्भुत खेल परिसर बा जवना में एथलेटिक्स, [[फुटबाल|फुटबॉल]], [[क्रिकेट]], [[वॉलीबॉल]], [[स्क्वैश]], [[टेनिस]], [[बैडमिंटन]], [[योग]], [[बास्केटबॉल]], [[बिलियर्ड्स]], [[टेबल टेनिस]] आदि के सुविधा बा। एह में आधुनिक जिम बा। एकरा अलावे प्रशिक्षु लोग के [[घोड़सवारी]], [[शूटिंग]], [[तीरंदाजी]], [[आत्मरक्षा]], [[पर्वतारोहण]] आदि के प्रशिक्षण भी दिहल जाला। अधिकारी प्रशिक्षु लोग के पर्वतारोहण ट्रेकिंग, रिवर राफ्टिंग, पैरासेलिंग आ बाकी एडवेंचर [[खेल]] खातीरो ले जाइल जाला। === ग्रन्थालय === [[गांधी स्मृति ग्रन्थालय]] के नाँव से चिन्हल जाए वाल एह ग्रन्थालय के लगे 1.65 लाख से ढेर [[किताब]], 5000 सीडी/डीवीडी, 300 गो पत्र आ पत्रिका आ 38 गो [[अखबार]] बाटे। फोटोकॉपी के सुविधो उपलब्ध बा। === औषधालय === प्रशिक्षणदेयी संस्था में डॉक्टर आ नर्सन के [[डाक्टरी (बिद्या)|मेडिकल]] केन्द्रो बा। एकरा अलावे एगो [[पेथोलॉजी]] प्रयोगशाला , एक्स-रे यूनिट, डेंटल अउरी [[फिजियोथेरेपी]] यूनिटो बा। === आईटी सेवा === प्रशिक्षणदेयी संस्था के [[कंप्यूटर]] सेक्शन डाटा प्रोसेसिंग आ हिसाब लेखा में लागल बा। एकरा अलावे एकरा में हाई स्पीड [[इंटरनेट]] कनेक्शन अउरी एडवांस [[सॉफ्टवेयर]] पेकेज बा। === अधिकारीयन के मेस === अधिकारीयन के मेस प्रशिक्षणदेयी संस्था के भीतर एगो खान-पान के संस्था बा। परिवीक्षाधीन लोग द्वारा चलावल जाए वाला ई जगहा परिवीक्षाधीन लोग के बीच वैश्विक भाईचारा आ बन्धुत्ता के भावना उपजावे खातीर कोशिश करेले। एकर जिम्मेदारी औपचारिक आ अनौपचारिक मिलन समारोह के आयोजन करे के बा। === इंदिरा भवन परिसर === ई सुविधा मुख्य परिसर से एक किलोमीटर दूर बा आ कक्षा में पढ़ावे आ कार्यशाला/सम्मेलन खातिर एगो विषेश सुविधा बा। [[लाल बहादुर शास्त्री]] राष्ट्रीय प्रशासनिक प्रशिक्षणदेयी संस्था में मौजूद सभे सुविधा में अलग-अलग सक्षम लोग प विचार शामिल बा। अकादमी के हर भवन में एक्सेस रैंप आ ब्रेल कोडित लिफ्ट लगावल गइल बा जवना के इस्तेमाल विकलांग व्यक्ति श्रेणी के अधिकारी प्रशिक्षु लोग करीहें। प्रशिक्षणदेयी संस्था अलग-अलग सक्षम प्रशिक्षु लोग के जरूरत के आधार पर एगो सहायक नियुक्त करेले ताकि रोजमर्रा के गतिविधि में मदद मिल सके। अलग-अलग क्षमता वाला लोग के उपयोग खातीर कैंपस के भीतर इलेक्ट्रिक रेक्सो बा। एकरा अलावे अलग-अलग क्षमता वाला सभ अधिकारी प्रशिक्षुवन के हेप्पी वैली हॉस्टल बिल्डिंग में ग्रन्थालय, ऑफिसर मेस आ कक्षा तक चहुपे के सुविधा दिहल जाला। लाल बहादुर शास्त्री राष्ट्रीय प्रशासनिक प्रशिक्षणदेयी संस्था के जीवन शिक्षण, रोमांच आ मस्ती से भरल बा। ई एगो परिवीक्षाधीन [[भारतीय प्रशासनिक सेवा|आई.ए.एस]] के प्रशिक्षु अधिकारी में ढाल देला आ ओह लोग के आई.ए.एस अधिकारी के काम करे खातिर जरूरी कौशल से परिपूर्ण करेला। == संदर्भ == {{Reflist|33em}} qa8rt51l3ysenwz4hhf8wu913v6jyix हवा क्वालिटी इंडेक्स 0 88639 803371 791990 2026-07-29T16:24:28Z SM7Bot 7808 /* भारत */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803371 wikitext text/x-wiki [[चित्र:Sha1993_smog_wkpd.jpg|thumb| चीन के शंघाई में [[स्मॉग|धुंध]], दिसंबर, 1993 के एगो तस्वीर : आम तौर पर सेहत के नोकसान पहुँचावे वाला लेवल के रेटिंग कइल जाए वाली हवा के स्थिति के एगो उदाहरण बाटे।]] [[चित्र:25August_2007_Greek_fires.jpg|thumb|एगो एनोटेटेड सैटेलाइट फोटो जवना में ग्रीस (यूनान) में जंगली आग के धुँआ देखावल गइल बा, जवना से हवा के एक्यूआई बढ़ल बा, मने की इस्थिति ख़राब भइल बाटे।]] '''हवा क्वालिटी इंडेक्स''' ({{Lang|en|air quality index}} (संछेप में '''AQI'''), {{lang|hi|वायु गुणवत्ता सूचकांक}}) के इस्तेमाल सरकारी एजेंसी सभ<ref>{{Cite web|url=http://www.airnow.gov/index.cfm?action=airnow.international|title=International Air Quality|archive-url=https://web.archive.org/web/20180612162706/https://www.airnow.gov/index.cfm?action=airnow.international|archive-date=12 June 2018|access-date=20 August 2015}}</ref> जनता के बतावे खातिर करे लीं कि वर्तमान में [[हवा प्रदूषण|हवा केतना प्रदूषित]] बा चाहे फिर केतना प्रदूषित होखे के अनुमान लगावल गइल बा।<ref>{{Cite web|url=https://airquality.weather.gov/|title=NOAA's National Weather Service/Environmental Protection Agency - United States Air Quality Forecast Guidance|website=airquality.weather.gov|access-date=2021-04-28}}</ref><ref>{{Cite web|url=https://www.gmes-atmosphere.eu/services/raq/raq_nrt/|title=MACC Project - European Air Quality Monitoring and Forecasting|archive-url=https://web.archive.org/web/20141018213242/https://www.gmes-atmosphere.eu/services/raq/raq_nrt/|archive-date=2014-10-18|access-date=2014-10-12}}</ref> एक्यूआई के जानकारी हवा के क्वालिटी सेंसर से औसतन रीडिंग से मिल जाला, जवन गाड़ी के आवाजाही, जंगल में आग लागे, या कवनो अइसन चीज के कारण बढ़ सके ला जेह से [[हवा प्रदूषण]] बढ़ सके ला। टेस्ट कइल जाये वाला प्रदूषक में [[ओजोन]], [[नाइट्रोजन डाइऑक्साइड]], [[सल्फर डाइऑक्साइड]], आ अउरी कई गो पदार्थ शामिल बाड़ें। एक्यूआई बढ़ला पर जनस्वास्थ्य के जोखिम बढ़ जाला, खासतौर पर बच्चा, बुजुर्ग आ साँस भा हृदय संबंधी समस्या वाला ब्यक्ति सभ के प्रभावित करे ला। एह समय सभ में सरकारी निकाय सभ आमतौर पर लोग हर शारीरिक गतिविधि कम करे खातिर प्रोत्साहित करे लीं, या फिर बाहर जाए से भी बिल्कुल परहेज करे के सलाह देवे लीं। कपड़ा के मास्क जइसन [[फेस मास्क]] के इस्तेमाल के भी सलाह दिहल जा सक'ता। अलग-अलग देस सभ के आपन-आपन हवा के गुणवत्ता के सूचकांक बनावे के तरीका होला जे अलग-अलग राष्ट्रीय हवा के गुणवत्ता मानक सभ के अनुरूप होला। एह में से कुछ कनाडा के हवा गुणवत्ता स्वास्थ्य सूचकांक, मलेशिया के वायु प्रदूषण सूचकांक, आ सिंगापुर के प्रदूषक इस्टैंडर सूचकांक बाड़ें। == संछेप में == [[चित्र:Edinburgh_air_quality_measurement_station_dsc06786.jpg|thumb| [[एडिनबर्ग]], [[स्कॉटलैंड]] में एगो हवा के गुणवत्ता माप स्टेशन]] एक्यूआई के गणना खातिर एगो निर्धारित औसत समय के दौरान हवा प्रदूषक के कंसंट्रेशन के नापजोख के जरूरत होला, जवन एयर मॉनिटर भा मॉडल से हासिल कइल गइल होला। एकरा के मिला के देखल जाय तब कंसंट्रेशन आ समय मिल के हवा प्रदूषक के डोज के बतावे ला। कौनों दिहल गइल डोज के अनुरूप स्वास्थ्य परभाव सभ के स्थापना [[महामारी|महामारी बिज्ञान]] के रिसर्च से कइल जाला।<ref>{{Cite web|url=http://www.epa.gov/risk_assessment/dose-response.htm|title=Step 2 - Dose-Response Assessment|date=26 September 2013|access-date=20 August 2015}}</ref> हवा प्रदूषक सभ के ताकत अलग-अलग होला आ हवा प्रदूषक के कंसंट्रेशन से एक्यूआई में बदले खातिर इस्तेमाल होखे वाला कामकाज प्रदूषक के हिसाब से अलग-अलग होला। एकर हवा के क्वालिटी इंडेक्स मान सभ के आमतौर पर रेंज में समूहबद्ध कइल जाला। हर रेंज के डिस्क्रिप्टर, रंग कोड बतावल जाला आ ओकरा हिसाब से स्टैंडर्ड जनस्वास्थ्य सलाह दिहल जाला। हवा के में प्रदूषक चीज छोड़े (एमिशन) में बढ़ोतरी के चलते एक्यूआई बढ़ सकता। उदाहरण खातिर, भीड़भाड़ के समय यातायात के दौरान या जब हवा के ऊपर जंगल में आग लागे ला या वायु प्रदूषक सभ के पतला होखे के कमी से। ठहरल हवा, अक्सर [[एंटीसाइक्लोन]], तापमान उलटफेर, या [[हवा]] के कम गति के कारण होखे वाली हवा के प्रदूषण के स्थानीय इलाका में रहे देला, जेकरा चलते प्रदूषक सभ के ढेर कंसंट्रेशन, हवा के दूषित पदार्थ सभ के बीच रासायनिक रिएक्शन आ धुंध के स्थिति पैदा हो जाले।<ref>{{Cite web|url=http://www.kjc.gov.my/english/education/weather/haze01.html|title=Haze|last=Myanmar government|date=2007|archive-url=https://web.archive.org/web/20070127214023/http://www.kjc.gov.my/english/education/weather/haze01.html|archive-date=27 January 2007|access-date=2007-02-11}}</ref> [[चित्र:SignboardAirQualityHouston.JPG|thumb| गल्फटन, ह्यूस्टन में साइनबोर्ड जवन [[ओजोन]] घड़ी के संकेत देत बा]] जवना दिन महीन कण प्रदूषण के कारण एक्यूआई के बढ़े के अनुमान लगावल जाला, कवनो एजेंसी भा जनस्वास्थ्य संगठन: * संवेदनशील समूह, जइसे कि बुजुर्ग, बच्चा, आ श्वसन भा हृदय संबंधी समस्या वाला लोग के सलाह देवे लीं कि बाहरी मेहनत से बचे के चाहीं.<ref>{{Cite web|url=http://www.lung.org/healthy-air/outdoor/air-quality-index.html|title=Air Quality Index - American Lung Association|website=American Lung Association|archive-url=https://web.archive.org/web/20150828143922/http://www.lung.org/healthy-air/outdoor/air-quality-index.html|archive-date=28 August 2015|access-date=20 August 2015}}</ref> * हवा के उत्सर्जन के कम करे खातिर स्वैच्छिक उपाय सभ के प्रोत्साहित करे खातिर "एक्शन डे" घोषित कइल, जइसे कि सार्वजनिक परिवहन के इस्तेमाल।<ref>{{Cite web|url=http://www.sparetheair.org/Make-a-Difference/Spare-the-Air-Every-Day/Summer.aspx|title=Spare the Air - Summer Spare the Air|archive-url=https://web.archive.org/web/20180611003730/http://sparetheair.org/Make-a-Difference/Spare-the-Air-Every-Day/Summer.aspx|archive-date=11 June 2018|access-date=20 August 2015}}</ref> * फेफड़ा में महीन कण के प्रवेश से बचावे खातिर मास्क के इस्तेमाल के सलाह दिहल जाला।<ref>{{Cite web|url=https://www.moh.gov.sg/content/moh_web/home/pressRoom/Current_Issues/2014/haze/faq--use-of-masks-and-availability-of-masks.html|title=FAQ: Use of masks and availability of masks|archive-url=https://web.archive.org/web/20180622004913/https://www.moh.gov.sg/content/moh_web/home/pressRoom/Current_Issues/2014/haze/faq--use-of-masks-and-availability-of-masks.html|archive-date=2018-06-22|access-date=20 August 2015}}</ref> बहुत खराब हवा के क्वालिटी के दौर के दौरान, जइसे कि वायु प्रदूषण के एपिसोड, जब एक्यूआई बतावे ला कि खतरनाक जोखिम से जनस्वास्थ्य के काफी नुकसान हो सके ला, एजेंसी सभ आपातकालीन योजना सभ के इस्तेमाल क सके लीं जेह में ऊ लोग प्रमुख उत्सर्जक (जइसे कि कोयला जरे वाला उद्योग) के ऑर्डर दे सके ला ) के उत्सर्जन में कटौती करे खातिर जब तक कि खतरनाक स्थिति कम ना हो जाव। अधिकतर हवा के दूषित पदार्थ सभ में एकरा से जुड़ल एक्यूआई ना होला। कई देस सभ जमीनी स्तर के ओजोन, [[पार्टिकुलेट मैटर|कण]], [[सल्फर डाइऑक्साइड]], कार्बन मोनोऑक्साइड आ नाइट्रोजन डाइऑक्साइड के निगरानी करे लें आ एह प्रदूषक सभ खातिर हवा क्वालिटी इंडेक्स के गणना करे लें।<ref name="aqi_basic">{{Cite web|url=http://www.airnow.gov/index.cfm?action=aqibasics.aqi|title=Air Quality Index (AQI) - A Guide to Air Quality and Your Health|date=9 December 2011|publisher=US EPA|archive-url=https://web.archive.org/web/20180618144741/https://airnow.gov/index.cfm?action=aqibasics.aqi|archive-date=18 June 2018|access-date=8 August 2012}}</ref> कवनो खास राष्ट्र में एक्यूआई के परिभाषा ओह राष्ट्र में राष्ट्रीय हवा के गुणवत्ता मानक के विकास के आसपास के प्रवचन के दर्शावेले।<ref>{{Cite web|url=https://www.wsj.com/articles/the-epas-latest-threat-to-economic-growth-1407970689|title=The EPA's Latest Threat to Economic Growth|last=Jay Timmons|date=13 August 2014|website=WSJ|access-date=20 August 2015}}</ref> दुनिया में कहीं भी सरकारी एजेंसी हवा के गुणवत्ता सूचकांक के एगो आम परिभाषा के इस्तेमाल से प्रदर्शन खातिर आपन रियल टाइम एयर मॉनिटरिंग डेटा जमा करे के इजाजत देवे वाली वेबसाइट हाल में उपलब्ध हो गइल बा।<ref>{{Cite web|url=http://waqi.info/|title=World Air Quality Index|access-date=20 August 2015}}</ref> == लोकेशन के हिसाब से इंडेक्स == === भारत === राष्ट्रीय वायु गुणवत्ता सूचकांक (AQI) के शुरुआत 17 सितंबर 2014 के [[नई दिल्ली]] में [[स्वच्छ भारत अभियान]] के तहत भइल रहे।<ref>{{Cite news|url=https://www.washingtonpost.com/blogs/worldviews/wp/2014/10/17/india-launches-its-own-air-quality-index-can-its-numbers-be-trusted/|title=India launches its own Air Quality Index. Can its numbers be trusted?|last=Rama Lakshmi|date=October 17, 2014|work=Washington Post|access-date=August 20, 2015}}</ref><ref>{{Cite news|url=https://www.business-standard.com/article/government-press-release/national-air-quality-index-aqi-launched-by-the-environment-minister-aqi-114101700961_1.html|title=National Air Quality Index (AQI) launched by the Environment Minister AQI is a huge initiative under 'Swachh Bharat' AQI to act as 'One Number- One Colour-One Description' to judge the Air Qualit|date=17 October 2014|work=Business Standard India}}</ref><ref>{{Cite news|url=https://www.firstpost.com/india/from-safar-to-national-air-quality-index-how-and-when-india-started-monitoring-aqi-10143261.html|title=Air Quality Index in India: When it was set up and how it is measured|date=17 November 2021|work=Firstpost|language=en}}</ref><ref>{{Cite news|url=https://pib.gov.in/newsite/printrelease.aspx?relid=110654#:~:text=National%20Air%20Quality%20Index%20(AQI,Environment%2C%20Forest%20and%20Climate%20Change.&text=The%20Minister%20for%20Environment%2C%20Forests,(AQI)%20in%20New%20Delhi.|title=National Air Quality Index (AQI) launched by the Environment Minister AQI is a huge initiative under 'Swachh Bharat'|work=pib.gov.in}}</ref> केंद्रीय प्रदूषण नियंत्रण बोर्ड राज्य प्रदूषण नियंत्रण बोर्डन के साथे मिल के देश के 240 शहरन के शामिल करत राष्ट्रीय वायु निगरानी कार्यक्रम (एनएएमपी) चलावत आइल बा जवना में 342 से अधिका निगरानी स्टेशन बा.<ref>{{Cite web|url=http://www.ppsthane.com/blog/ambient-air-quality-monitoring-stations|title=Ambient Air Quality Monitoring Stations|date=2016-08-15|language=en-US|access-date=2016-08-16}}</ref> मेडिकल प्रोफेशनल, एयर क्वालिटी एक्सपर्ट, अकादमी, एडवोकेसी ग्रुप, आ एसपीसीबी के एगो एक्सपर्ट ग्रुप के गठन कइल गइल आ आईआईटी कानपुर के एगो तकनीकी अध्ययन दिहल गइल. आईआईटी कानपुर आ एक्सपर्ट ग्रुप 2014 में एक्यूआई योजना के सिफारिश कइलस।<ref>{{Cite web|url=http://home.iitk.ac.in/~mukesh/indian%20air%20quality.html|title=Indian Air Quality Index|archive-url=https://web.archive.org/web/20141107224911/http://home.iitk.ac.in/~mukesh/indian%20air%20quality.html|archive-date=2014-11-07|access-date=2014-10-21}}</ref> जबकि पहिले के माप सूचकांक तीन संकेतक तक सीमित रहे, लेकिन नयका सूचकांक में आठ पैरामीटर के माप कइल गइल बा।<ref>{{Cite web|url=http://pib.nic.in/newsite/PrintRelease.aspx?relid=110654|title=National Air Quality Index (AQI) launched by the Environment Minister AQI is a huge initiative under 'Swachh Bharat'|access-date=20 August 2015}}</ref> लगभग रियल टाइम आधार पर डेटा देवे वाला लगातार निगरानी प्रणाली नई दिल्ली, [[मुंबई]], [[पुणे]], [[कलकत्ता|कोलकाता]] अउरी [[अहमदाबाद]] में लगावल गइल बा .<ref>{{Cite news|url=http://economictimes.indiatimes.com/news/politics-and-nation/india-launches-index-to-measure-air-quality/etvertical_articleshow/44855452.cms|title=India launches index to measure air quality|work=timesofindia-economictimes|access-date=20 August 2015}}</ref> एक्यूआई के छह गो श्रेणी बा, जवना में अच्छा, संतोषजनक, मध्यम, खराब, गंभीर, आ खतरनाक बा। प्रस्तावित एक्यूआई में आठ गो प्रदूषक (PM {{Sub|10}}, PM {{Sub|2.5}}, NO {{Sub|2}}, SO {{Sub|2}}, CO, O {{Sub|3}}, NH {{Sub|3}}, आ Pb) पर बिचार कइल जाई जिनहन खातिर अल्पकालिक (24 घंटा के औसत अवधि तक) राष्ट्रीय परिवेश हवा के गुणवत्ता मानक निर्धारित कइल जाला।<ref>{{Cite web|url=http://www.cpcb.nic.in|title=::: Central Pollution Control Board :::|access-date=20 August 2015}}</ref> नापल गइल परिवेश के एकाग्रता, संबंधित मानक आ संभावित स्वास्थ्य परभाव के आधार पर, एह में से हर प्रदूषक खातिर एगो उप-सूचकांक के गणना कइल जाला। सबसे खराब उप-सूचकांक समग्र एक्यूआई के दर्शावत बा। अलग-अलग एक्यूआई श्रेणी आ प्रदूषक सभ खातिर संभावित स्वास्थ्य परभाव के भी सुझाव दिहल गइल बा, समूह के मेडिकल बिसेसज्ञ लोग के प्राथमिक इनपुट के साथ। एक्यूआई मान आ संबंधित परिवेश के एकाग्रता (स्वास्थ्य ब्रेकपॉइंट) के साथे-साथ चिन्हित आठ गो प्रदूषक सभ खातिर संबंधित संभावित स्वास्थ्य परभाव निम्नलिखित बाड़ें: {| class="wikitable" |+एक्यूआई श्रेणी, प्रदूषक आ स्वास्थ्य ब्रेकपॉइंट | style="background: #9cf; colour: black; font-weight:bold" | एक्यूआई श्रेणी (रेंज) । | style="background: #9cf; colour: black; font-weight:bold" | पीएम {{Sub|10}} (24 घंटा) । | style="background: #9cf; colour: black; font-weight:bold" | पीएम {{Sub|2.5}} (24 घंटा) । | style="background: #9cf; colour: black; font-weight:bold" | नंबर {{Sub|2}} (24घंटा) | style="background: #9cf; colour: black; font-weight:bold" | ओ {{Sub|3}} (8घंटा) | style="background: #9cf; colour: black; font-weight:bold" | सीओ (8घंटा) | style="background: #9cf; colour: black; font-weight:bold" | एसओ {{Sub|2}} (24घंटा) | style="background: #9cf; colour: black; font-weight:bold" | एनएच {{Sub|3}} (24घंटा) | style="background: #9cf; colour: black; font-weight:bold" | पीबी (24 घंटा) | style="background: #9cf; colour: black; font-weight:bold" | रंग |- | style="background:#00b050; colour: black" | बढ़िया (0-50) | style="background:#00b050; colour: black" | 0-50 | style="background:#00b050; colour: black" | 0-30 | style="background:#00b050; colour: black" | 0-40 | style="background:#00b050; colour: black" | 0-50 | style="background:#00b050; colour: black" | 0-1.0 | style="background:#00b050; colour: black" | 0-40 | style="background:#00b050; colour: black" | 0-200 | style="background:#00b050; colour: black" | 0-0.5 | style="background:#00b050; colour: black" | गहिराह हरियर रंग |- | style="background:#92d050; colour: black" | संतोषजनक (51-100) | style="background:#92d050; colour: black" | 51-100 | style="background:#92d050; colour: black" | 31-60 | style="background:#92d050; colour: black" | 41-80 | style="background:#92d050; colour: black" | 51-100 | style="background:#92d050; colour: black" | 1.1-2.0 | style="background:#92d050; colour: black" | 41-80 | style="background:#92d050; colour: black" | 201-400 | style="background:#92d050; colour: black" | 0.5-1.0 | style="background:#92d050; colour: black" | हल्का हरियर रंग के होला |- | style="background: #ff0; colour: black" | मध्यम (101-200) | style="background: #ff0; colour: black" | 101-250 | style="background: #ff0; colour: black" | 61-90 | style="background: #ff0; colour: black" | 81-180 | style="background: #ff0; colour: black" | 101-168 | style="background: #ff0; colour: black" | 2.1-10 | style="background: #ff0; colour: black" | 81-380 | style="background: #ff0; colour: black" | 401-800 | style="background: #ff0; colour: black" | 1.1-2.0 | style="background: #ff0; colour: black" | पियर |- | style="background: #ff6500; colour: black" | गरीब (201-300) | style="background: #ff6500; colour: black" | 251-350 | style="background: #ff6500; colour: black" | 91-120 | style="background: #ff6500; colour: black" | 181-280 | style="background: #ff6500; colour: black" | 169-208 | style="background: #ff6500; colour: black" | 10-17 | style="background: #ff6500; colour: black" | 381-800 | style="background: #ff6500; colour: black" | 801-1200 | style="background: #ff6500; colour: black" | 2.1-3.0 | style="background: #ff6500; colour: black" | संतरा |- | style="background: #ff0000; color: white" | गंभीर (301-400) | style="background: #ff0000; color: white" | 351-430 | style="background: #ff0000; color: white" | 121-250 | style="background: #ff0000; color: white" | 281-400 | style="background: #ff0000; color: white" | 209-748 | style="background: #ff0000; color: white" | 17-34 | style="background: #ff0000; color: white" | 801-1600 | style="background: #ff0000; color: white" | 1200-1800 | style="background: #ff0000; color: white" | 3.1-3.5 | style="background: #ff0000; color: white" | लाल |- | style="background: #c00000; color: white" | खतरनाक (401-500) | style="background: #c00000; color: white" | 430+ | style="background: #c00000; color: white" | 250+ | style="background: #c00000; color: white" | 400+ | style="background: #c00000; color: white" | 748+ | style="background: #c00000; color: white" | 34+ | style="background: #c00000; color: white" | 1600+ | style="background: #c00000; color: white" | 1800+ | style="background: #c00000; color: white" | 3.5+ | style="background: #c00000; color: white" | मैरून |} {| class="wikitable" !एक्यूआई ! सेहत पर परभाव |- | बढ़िया (0-50) | कम से कम असर पड़े |- | संतोषजनक (51-100) | संवेदनशील लोग के सांस लेवे में मामूली परेशानी हो सक ता |- | मध्यम (101-200) | फेफड़ा के बेमारी जइसे कि दमा से पीड़ित लोग के साँस लेवे में परेशानी हो सकेला, आ दिल के बेमारी से पीड़ित लोग |- | गरीब (201-300) | लंबा समय तक संपर्क में रहला प लोग के सांस लेवे में परेशानी हो सक ता |- | बहुत गरीब (301-400) | लंबा समय तक संपर्क में रहला प लोग के सांस के बेमारी हो सक ता |- | गम्हीर (401-500) | स्वस्थ लोग पर भी साँस के प्रभाव पड़ सकेला, आ फेफड़ा/हृदय के बेमारी से पीड़ित लोग पर स्वास्थ्य पर गंभीर प्रभाव पड़ सकेला |} === अमेरिका === [[चित्र:Pm25-24a-super.gif|thumb|पीएम <sub>2.5</sub> 24 घंटा के एक्यूआई लूप, यूएस ईपीए के सौजन्य से]] अमेरिका के पर्यावरण संरक्षण एजेंसी (ईपीए) एगो हवा के गुणवत्ता सूचकांक बनवले बिया जवना के इस्तेमाल हवा के गुणवत्ता के रिपोर्ट करे खातिर कइल जाला. एह एक्यूआई के छह श्रेणी में बाँटल गइल बा जवन स्वास्थ्य चिंता के बढ़त स्तर के संकेत देत बा। 300 से ढेर एक्यूआई मान खतरनाक हवा के गुणवत्ता के प्रतिनिधित्व करे ला आ 50 से नीचे हवा के गुणवत्ता बढ़िया होला।<ref name="aqi_basic" /> {| | {| style="width:480px; float:left;" cellspacing="0" border="2" | style="width:31%;" | '''<span style="color:#fff; font-family:arial; font-size:1em;">हवा के गुणवत्ता सूचकांक (एक्यूआई) के मान</span>''' | style="background:#036; width:32%;" | <span style="color:#fff; font-family:arial; font-size:1em;">'''स्वास्थ्य के चिंता के स्तर'''</span> | style="background:#036; width:37%;" | <span style="color:#fff; font-family:arial; font-size:1em;">'''रंग-बिरंग '''</span> |- style="vertical-align:top; background:#00e400;" | style="width:31%;" | 0 से 50 तक | style="width:32%;" | नीमन | style="width:37%;" | हरियर |- style="vertical-align:top; background:#ff0;" | style="width:31%;" | 51 से 100 तक | style="width:32%;" | उदार | style="width:37%;" | पियर |- style="vertical-align:top; background:#ff7e00;" | style="width:31%;" | 101 से 150 तक | style="width:32%;" | संवेदनशील समूह खातिर अस्वस्थ बा | style="width:37%;" | संतरा |- style="vertical-align:top; background:#f00;" | style="width:31%;" | <span style="color:#fff;">151 से 200 तक </span> | style="width:32%;" | <span style="color:#fff;">अस्वस्थ बा</span> | style="width:37%;" | <span style="color:#fff;">लाल</span> |- style="vertical-align:top; background:#8f3f97;" | style="width:31%;" | <span style="color:#fff;">201 से 300 तक </span> | style="width:32%;" | <span style="color:#fff;">बहुत अस्वस्थ बा</span> | style="width:37%;" | <span style="color:#fff;">बैंगनी</span> |- style="vertical-align:top; background:#7e0023;" | style="width:31%;" | <span style="color:#fff;">301 से 500 तक </span> | style="width:32%;" | <span style="color:#fff;">खतरनाक बा</span> | style="width:37%;" | <span style="color:#fff;">मैरून </span> |} |} एक्यूआई स्वच्छ हवा अधिनियम के तहत नियंत्रित पाँच गो "मापदण्ड" प्रदूषक सभ पर आधारित बा : जमीनी स्तर के ओजोन, कण पदार्थ, कार्बन मोनोऑक्साइड, सल्फर डाइऑक्साइड, आ नाइट्रोजन डाइऑक्साइड। ईपीए एह में से हर प्रदूषक खातिर नेशनल एम्बिएंट एयर क्वालिटी स्टैंडर्ड (NAAQS) के स्थापना कइले बा जेह से कि जनस्वास्थ्य के रक्षा कइल जा सके। 100 के AQI मान आमतौर पर प्रदूषक खातिर NAAQS के स्तर से मेल खाला।<ref name="aqi_basic" /> स्वच्छ हवा अधिनियम (अमेरिका) (1990) में ईपीए के हर पांच साल पर अपना राष्ट्रीय परिवेश हवा के गुणवत्ता मानक के समीक्षा करे के कहल गइल बा ताकि स्वास्थ्य परभाव के बिकसित जानकारी के देखावल जा सके। एह बदलावन के देखावे खातिर हवा के गुणवत्ता सूचकांक में समय-समय पर समायोजन कइल जाला। {{clear}} == इहो देखल जाय == * [[हवा प्रदूषण]] * [[पार्टिकुलेट मैटर]] == संदर्भ == {{Reflist|33em}} == बाहरी कड़ी == * [https://www.aqi.in/ भारत में एक्यूआई], रियल टाइम आँकड़ा देखे खातिर। {{प्रदूषण}} [[श्रेणी:प्रदूषण]] [[श्रेणी:हवा प्रदूषण]] [[श्रेणी:पृथिवी के वायुमंडल]] n6ntotpf4yqniphi44pk7n4yr2yz0yq Module:Infobox/styles.css 828 88925 803575 736022 2024-04-08T01:53:22Z en>Jdlrobson 0 Apply night theme to infobox full data so links are accessible 803575 sanitized-css text/css /* {{pp|small=y}} */ /* * This TemplateStyles sheet deliberately does NOT include the full set of * infobox styles. We are still working to migrate all of the manual * infoboxes. See [[MediaWiki talk:Common.css/to do#Infobox]] * DO NOT ADD THEM HERE */ /* * not strictly certain these styles are necessary since the modules now * exclusively output infobox-subbox or infobox, not both * just replicating the module faithfully */ .infobox-subbox { padding: 0; border: none; margin: -3px; width: auto; min-width: 100%; font-size: 100%; clear: none; float: none; background-color: transparent; } .infobox-3cols-child { margin: auto; } .infobox .navbar { font-size: 100%; } /* T281642 */ body.skin-minerva .infobox-header, body.skin-minerva .infobox-subheader, body.skin-minerva .infobox-above, body.skin-minerva .infobox-title, body.skin-minerva .infobox-image, body.skin-minerva .infobox-full-data, body.skin-minerva .infobox-below { text-align: center; } /* Dark theme: */ html.skin-theme-clientpref-night .infobox-full-data * { background: #37373e !important; } @media (prefers-color-scheme: dark) { html.skin-theme-clientpref-os .infobox-full-data * { background: #37373e !important; } } 8wvaa7xentd53bc6gbh49loqk1wudb3 803576 803575 2024-04-08T01:55:17Z en>Jdlrobson 0 Add some example pages and slightly darken background 803576 sanitized-css text/css /* {{pp|small=y}} */ /* * This TemplateStyles sheet deliberately does NOT include the full set of * infobox styles. We are still working to migrate all of the manual * infoboxes. See [[MediaWiki talk:Common.css/to do#Infobox]] * DO NOT ADD THEM HERE */ /* * not strictly certain these styles are necessary since the modules now * exclusively output infobox-subbox or infobox, not both * just replicating the module faithfully */ .infobox-subbox { padding: 0; border: none; margin: -3px; width: auto; min-width: 100%; font-size: 100%; clear: none; float: none; background-color: transparent; } .infobox-3cols-child { margin: auto; } .infobox .navbar { font-size: 100%; } /* T281642 */ body.skin-minerva .infobox-header, body.skin-minerva .infobox-subheader, body.skin-minerva .infobox-above, body.skin-minerva .infobox-title, body.skin-minerva .infobox-image, body.skin-minerva .infobox-full-data, body.skin-minerva .infobox-below { text-align: center; } /* Dark theme: [[William_Wragg]], [[Coral_Castle]] */ html.skin-theme-clientpref-night .infobox-full-data * { background: #1f1f23 !important; } @media (prefers-color-scheme: dark) { html.skin-theme-clientpref-os .infobox-full-data * { background: #1f1f23 !important; } } pquqexq7eyt2nfewo6qikaml34jw9ru 803577 803576 2024-04-08T01:57:25Z en>Jdlrobson 0 .... and of course color-base is needed. 803577 sanitized-css text/css /* {{pp|small=y}} */ /* * This TemplateStyles sheet deliberately does NOT include the full set of * infobox styles. We are still working to migrate all of the manual * infoboxes. See [[MediaWiki talk:Common.css/to do#Infobox]] * DO NOT ADD THEM HERE */ /* * not strictly certain these styles are necessary since the modules now * exclusively output infobox-subbox or infobox, not both * just replicating the module faithfully */ .infobox-subbox { padding: 0; border: none; margin: -3px; width: auto; min-width: 100%; font-size: 100%; clear: none; float: none; background-color: transparent; } .infobox-3cols-child { margin: auto; } .infobox .navbar { font-size: 100%; } /* T281642 */ body.skin-minerva .infobox-header, body.skin-minerva .infobox-subheader, body.skin-minerva .infobox-above, body.skin-minerva .infobox-title, body.skin-minerva .infobox-image, body.skin-minerva .infobox-full-data, body.skin-minerva .infobox-below { text-align: center; } /* Dark theme: [[William_Wragg]], [[Coral_Castle]] */ html.skin-theme-clientpref-night .infobox-full-data * { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } @media (prefers-color-scheme: dark) { html.skin-theme-clientpref-os .infobox-full-data * { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } tvhiop4s8igknittm8zr1gazaetnqp6 803578 803577 2024-04-09T15:09:30Z en>Jdlrobson 0 Use div so this doesn't apply to 803578 sanitized-css text/css /* {{pp|small=y}} */ /* * This TemplateStyles sheet deliberately does NOT include the full set of * infobox styles. We are still working to migrate all of the manual * infoboxes. See [[MediaWiki talk:Common.css/to do#Infobox]] * DO NOT ADD THEM HERE */ /* * not strictly certain these styles are necessary since the modules now * exclusively output infobox-subbox or infobox, not both * just replicating the module faithfully */ .infobox-subbox { padding: 0; border: none; margin: -3px; width: auto; min-width: 100%; font-size: 100%; clear: none; float: none; background-color: transparent; } .infobox-3cols-child { margin: auto; } .infobox .navbar { font-size: 100%; } /* T281642 */ body.skin-minerva .infobox-header, body.skin-minerva .infobox-subheader, body.skin-minerva .infobox-above, body.skin-minerva .infobox-title, body.skin-minerva .infobox-image, body.skin-minerva .infobox-full-data, body.skin-minerva .infobox-below { text-align: center; } /* Dark theme: [[William_Wragg]], [[Coral_Castle]] */ html.skin-theme-clientpref-night .infobox-full-data div { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } @media (prefers-color-scheme: dark) { html.skin-theme-clientpref-os .infobox-full-data div { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } n794oomzam5ytazh5fgew7phq5hivoe 803579 803578 2024-06-12T18:43:28Z en>Jdlrobson 0 Restrict to elements not tagged notheme to allow templates to override 803579 sanitized-css text/css /* {{pp|small=y}} */ /* * This TemplateStyles sheet deliberately does NOT include the full set of * infobox styles. We are still working to migrate all of the manual * infoboxes. See [[MediaWiki talk:Common.css/to do#Infobox]] * DO NOT ADD THEM HERE */ /* * not strictly certain these styles are necessary since the modules now * exclusively output infobox-subbox or infobox, not both * just replicating the module faithfully */ .infobox-subbox { padding: 0; border: none; margin: -3px; width: auto; min-width: 100%; font-size: 100%; clear: none; float: none; background-color: transparent; } .infobox-3cols-child { margin: auto; } .infobox .navbar { font-size: 100%; } /* T281642 */ body.skin-minerva .infobox-header, body.skin-minerva .infobox-subheader, body.skin-minerva .infobox-above, body.skin-minerva .infobox-title, body.skin-minerva .infobox-image, body.skin-minerva .infobox-full-data, body.skin-minerva .infobox-below { text-align: center; } /* Dark theme: [[William_Wragg]], [[Coral_Castle]] */ html.skin-theme-clientpref-night .infobox-full-data:not(.notheme) div:not(.notheme) { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } @media (prefers-color-scheme: dark) { html.skin-theme-clientpref-os .infobox-full-data:not(.notheme) div:not(.notheme) { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } 8o1tllmn0leabq7gu8vk3fyiu6kz5hl 803580 803579 2024-06-13T21:46:42Z en>Jon (WMF) 0 Applies fix for [[File:Broken_infobox_of_Chris_Dangerfield_2024-06-13.png]] 803580 sanitized-css text/css /* {{pp|small=y}} */ /* * This TemplateStyles sheet deliberately does NOT include the full set of * infobox styles. We are still working to migrate all of the manual * infoboxes. See [[MediaWiki talk:Common.css/to do#Infobox]] * DO NOT ADD THEM HERE */ /* * not strictly certain these styles are necessary since the modules now * exclusively output infobox-subbox or infobox, not both * just replicating the module faithfully */ .infobox-subbox { padding: 0; border: none; margin: -3px; width: auto; min-width: 100%; font-size: 100%; clear: none; float: none; background-color: transparent; } .infobox-3cols-child { margin: auto; } .infobox .navbar { font-size: 100%; } /* T281642 */ body.skin-minerva .infobox-header, body.skin-minerva .infobox-subheader, body.skin-minerva .infobox-above, body.skin-minerva .infobox-title, body.skin-minerva .infobox-image, body.skin-minerva .infobox-full-data, body.skin-minerva .infobox-below { text-align: center; } /* Overrides restriction of max width on infoboxes until templates such as [[Template:Infobox_football_biography]] have been adopted for mobile screens ([[File:Broken_infobox_of_Chris_Dangerfield_2024-06-13.png]]) */ body.skin--responsive.skin-vector-2022 .mw-parser-output .infobox { max-width: none !important; } /* Dark theme: [[William_Wragg]], [[Coral_Castle]] */ html.skin-theme-clientpref-night .infobox-full-data:not(.notheme) div:not(.notheme) { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } @media (prefers-color-scheme: dark) { html.skin-theme-clientpref-os .infobox-full-data:not(.notheme) div:not(.notheme) { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } lj0tohecldp0bwj0b2u1qej5bqtrdyi 803581 803580 2024-06-13T21:49:48Z en>Jon (WMF) 0 Drop parser output and add media query. Follow up to previous edit. 803581 sanitized-css text/css /* {{pp|small=y}} */ /* * This TemplateStyles sheet deliberately does NOT include the full set of * infobox styles. We are still working to migrate all of the manual * infoboxes. See [[MediaWiki talk:Common.css/to do#Infobox]] * DO NOT ADD THEM HERE */ /* * not strictly certain these styles are necessary since the modules now * exclusively output infobox-subbox or infobox, not both * just replicating the module faithfully */ .infobox-subbox { padding: 0; border: none; margin: -3px; width: auto; min-width: 100%; font-size: 100%; clear: none; float: none; background-color: transparent; } .infobox-3cols-child { margin: auto; } .infobox .navbar { font-size: 100%; } /* T281642 */ body.skin-minerva .infobox-header, body.skin-minerva .infobox-subheader, body.skin-minerva .infobox-above, body.skin-minerva .infobox-title, body.skin-minerva .infobox-image, body.skin-minerva .infobox-full-data, body.skin-minerva .infobox-below { text-align: center; } /* Overrides restriction of max width on infoboxes until templates such as [[Template:Infobox_football_biography]] have been adopted for mobile screens ([[File:Broken_infobox_of_Chris_Dangerfield_2024-06-13.png]]) */ @media (min-width: 640px) { body.skin--responsive.skin-vector-2022 .infobox { max-width: none !important; } } /* Dark theme: [[William_Wragg]], [[Coral_Castle]] */ html.skin-theme-clientpref-night .infobox-full-data:not(.notheme) div:not(.notheme) { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } @media (prefers-color-scheme: dark) { html.skin-theme-clientpref-os .infobox-full-data:not(.notheme) div:not(.notheme) { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } b4p3hfepnkcv3u5ikna79azc0nx9dfm 803582 803581 2024-06-13T22:16:17Z en>Jon (WMF) 0 Rollback. This has unexpected consequences on other pages e.g. [[Spain]] 803582 sanitized-css text/css /* {{pp|small=y}} */ /* * This TemplateStyles sheet deliberately does NOT include the full set of * infobox styles. We are still working to migrate all of the manual * infoboxes. See [[MediaWiki talk:Common.css/to do#Infobox]] * DO NOT ADD THEM HERE */ /* * not strictly certain these styles are necessary since the modules now * exclusively output infobox-subbox or infobox, not both * just replicating the module faithfully */ .infobox-subbox { padding: 0; border: none; margin: -3px; width: auto; min-width: 100%; font-size: 100%; clear: none; float: none; background-color: transparent; } .infobox-3cols-child { margin: auto; } .infobox .navbar { font-size: 100%; } /* T281642 */ body.skin-minerva .infobox-header, body.skin-minerva .infobox-subheader, body.skin-minerva .infobox-above, body.skin-minerva .infobox-title, body.skin-minerva .infobox-image, body.skin-minerva .infobox-full-data, body.skin-minerva .infobox-below { text-align: center; } /* Dark theme: [[William_Wragg]], [[Coral_Castle]] */ html.skin-theme-clientpref-night .infobox-full-data:not(.notheme) div:not(.notheme) { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } @media (prefers-color-scheme: dark) { html.skin-theme-clientpref-os .infobox-full-data:not(.notheme) div:not(.notheme) { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } 8o1tllmn0leabq7gu8vk3fyiu6kz5hl 803583 803582 2024-06-14T01:15:22Z en>Jon (WMF) 0 Add infobox-table class to markup infoboxes that rely on the table tag that is associated with infoboxes. 803583 sanitized-css text/css /* {{pp|small=y}} */ /* * This TemplateStyles sheet deliberately does NOT include the full set of * infobox styles. We are still working to migrate all of the manual * infoboxes. See [[MediaWiki talk:Common.css/to do#Infobox]] * DO NOT ADD THEM HERE */ /* * not strictly certain these styles are necessary since the modules now * exclusively output infobox-subbox or infobox, not both * just replicating the module faithfully */ .infobox-subbox { padding: 0; border: none; margin: -3px; width: auto; min-width: 100%; font-size: 100%; clear: none; float: none; background-color: transparent; } .infobox-3cols-child { margin: auto; } .infobox .navbar { font-size: 100%; } /* T281642 */ body.skin-minerva .infobox-header, body.skin-minerva .infobox-subheader, body.skin-minerva .infobox-above, body.skin-minerva .infobox-title, body.skin-minerva .infobox-image, body.skin-minerva .infobox-full-data, body.skin-minerva .infobox-below { text-align: center; } /* Dark theme: [[William_Wragg]], [[Coral_Castle]] */ html.skin-theme-clientpref-night .infobox-full-data:not(.notheme) div:not(.notheme) { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } @media (prefers-color-scheme: dark) { html.skin-theme-clientpref-os .infobox-full-data:not(.notheme) div:not(.notheme) { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } /* Since infobox is a table, many infobox templates take advantage of this to add columns and rows to the infobox itself rather than as part of a new table inside them. This class should be discouraged and removed on the long term, but allows us to at least identify these tables going forward Currently in use on: [[Module:Infobox3cols]] Fixes issue described in https://phabricator.wikimedia.org/F55300125 on Vector 2022. */ @media (min-width: 640px) { body.skin--responsive .infobox-table { display: table !important; } body.skin--responsive .infobox-table > tbody, body.skin--responsive .infobox-table > caption, body.skin--responsive .infobox-table tr { display: table-row !important; } } emm86c0lv2x2y4ikxjx06ztp6ejc9mw 803584 803583 2024-06-14T03:50:54Z en>Jdlrobson 0 Reduce padding, to reduce overall size of infobox-table elements 803584 sanitized-css text/css /* {{pp|small=y}} */ /* * This TemplateStyles sheet deliberately does NOT include the full set of * infobox styles. We are still working to migrate all of the manual * infoboxes. See [[MediaWiki talk:Common.css/to do#Infobox]] * DO NOT ADD THEM HERE */ /* * not strictly certain these styles are necessary since the modules now * exclusively output infobox-subbox or infobox, not both * just replicating the module faithfully */ .infobox-subbox { padding: 0; border: none; margin: -3px; width: auto; min-width: 100%; font-size: 100%; clear: none; float: none; background-color: transparent; } .infobox-3cols-child { margin: auto; } .infobox .navbar { font-size: 100%; } /* T281642 */ body.skin-minerva .infobox-header, body.skin-minerva .infobox-subheader, body.skin-minerva .infobox-above, body.skin-minerva .infobox-title, body.skin-minerva .infobox-image, body.skin-minerva .infobox-full-data, body.skin-minerva .infobox-below { text-align: center; } /* Dark theme: [[William_Wragg]], [[Coral_Castle]] */ html.skin-theme-clientpref-night .infobox-full-data:not(.notheme) div:not(.notheme) { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } @media (prefers-color-scheme: dark) { html.skin-theme-clientpref-os .infobox-full-data:not(.notheme) div:not(.notheme) { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } /* Since infobox is a table, many infobox templates take advantage of this to add columns and rows to the infobox itself rather than as part of a new table inside them. This class should be discouraged and removed on the long term, but allows us to at least identify these tables going forward Currently in use on: [[Module:Infobox3cols]] Fixes issue described in https://phabricator.wikimedia.org/F55300125 on Vector 2022. */ @media (min-width: 640px) { body.skin--responsive .infobox-table { display: table !important; } body.skin--responsive .infobox-table > tbody, body.skin--responsive .infobox-table > caption, body.skin--responsive .infobox-table tr { display: table-row !important; } body.skin--responsive .infobox-table th, body.skin--responsive .infobox-table td { padding-left: inherit; padding-right: inherit; } } rua0wcb71j23d3o3okm2ve04d8d3k1l 803585 803584 2024-06-14T03:58:50Z en>Jdlrobson 0 Limit the !important's to inline style override 803585 sanitized-css text/css /* {{pp|small=y}} */ /* * This TemplateStyles sheet deliberately does NOT include the full set of * infobox styles. We are still working to migrate all of the manual * infoboxes. See [[MediaWiki talk:Common.css/to do#Infobox]] * DO NOT ADD THEM HERE */ /* * not strictly certain these styles are necessary since the modules now * exclusively output infobox-subbox or infobox, not both * just replicating the module faithfully */ .infobox-subbox { padding: 0; border: none; margin: -3px; width: auto; min-width: 100%; font-size: 100%; clear: none; float: none; background-color: transparent; } .infobox-3cols-child { margin: auto; } .infobox .navbar { font-size: 100%; } /* T281642 */ body.skin-minerva .infobox-header, body.skin-minerva .infobox-subheader, body.skin-minerva .infobox-above, body.skin-minerva .infobox-title, body.skin-minerva .infobox-image, body.skin-minerva .infobox-full-data, body.skin-minerva .infobox-below { text-align: center; } /* Dark theme: [[William_Wragg]], [[Coral_Castle]] */ html.skin-theme-clientpref-night .infobox-full-data:not(.notheme) > div:not(.notheme)[style] { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } @media (prefers-color-scheme: dark) { html.skin-theme-clientpref-os .infobox-full-data:not(.notheme) div:not(.notheme) { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } /* Since infobox is a table, many infobox templates take advantage of this to add columns and rows to the infobox itself rather than as part of a new table inside them. This class should be discouraged and removed on the long term, but allows us to at least identify these tables going forward Currently in use on: [[Module:Infobox3cols]] Fixes issue described in https://phabricator.wikimedia.org/F55300125 on Vector 2022. */ @media (min-width: 640px) { body.skin--responsive .infobox-table { display: table !important; } body.skin--responsive .infobox-table > tbody, body.skin--responsive .infobox-table > caption, body.skin--responsive .infobox-table tr { display: table-row !important; } body.skin--responsive .infobox-table th, body.skin--responsive .infobox-table td { padding-left: inherit; padding-right: inherit; } } biq4f9es7pzhqssg4qc5dos3kfefhkl 803586 803585 2024-06-14T22:36:41Z en>Jdlrobson 0 Use display table-caption for captions 803586 sanitized-css text/css /* {{pp|small=y}} */ /* * This TemplateStyles sheet deliberately does NOT include the full set of * infobox styles. We are still working to migrate all of the manual * infoboxes. See [[MediaWiki talk:Common.css/to do#Infobox]] * DO NOT ADD THEM HERE */ /* * not strictly certain these styles are necessary since the modules now * exclusively output infobox-subbox or infobox, not both * just replicating the module faithfully */ .infobox-subbox { padding: 0; border: none; margin: -3px; width: auto; min-width: 100%; font-size: 100%; clear: none; float: none; background-color: transparent; } .infobox-3cols-child { margin: auto; } .infobox .navbar { font-size: 100%; } /* T281642 */ body.skin-minerva .infobox-header, body.skin-minerva .infobox-subheader, body.skin-minerva .infobox-above, body.skin-minerva .infobox-title, body.skin-minerva .infobox-image, body.skin-minerva .infobox-full-data, body.skin-minerva .infobox-below { text-align: center; } /* Dark theme: [[William_Wragg]], [[Coral_Castle]] */ html.skin-theme-clientpref-night .infobox-full-data:not(.notheme) > div:not(.notheme)[style] { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } @media (prefers-color-scheme: dark) { html.skin-theme-clientpref-os .infobox-full-data:not(.notheme) div:not(.notheme) { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } /* Since infobox is a table, many infobox templates take advantage of this to add columns and rows to the infobox itself rather than as part of a new table inside them. This class should be discouraged and removed on the long term, but allows us to at least identify these tables going forward Currently in use on: [[Module:Infobox3cols]] Fixes issue described in https://phabricator.wikimedia.org/F55300125 on Vector 2022. */ @media (min-width: 640px) { body.skin--responsive .infobox-table { display: table !important; } body.skin--responsive .infobox-table > caption { display: table-caption !important; } body.skin--responsive .infobox-table > tbody, body.skin--responsive .infobox-table tr { display: table-row !important; } body.skin--responsive .infobox-table th, body.skin--responsive .infobox-table td { padding-left: inherit; padding-right: inherit; } } 1mum59t79jnsox9rh835onlc51dys1n 803587 803586 2024-06-14T23:05:28Z en>Jdlrobson 0 For completeness.. use table-row-group for tbody element 803587 sanitized-css text/css /* {{pp|small=y}} */ /* * This TemplateStyles sheet deliberately does NOT include the full set of * infobox styles. We are still working to migrate all of the manual * infoboxes. See [[MediaWiki talk:Common.css/to do#Infobox]] * DO NOT ADD THEM HERE */ /* * not strictly certain these styles are necessary since the modules now * exclusively output infobox-subbox or infobox, not both * just replicating the module faithfully */ .infobox-subbox { padding: 0; border: none; margin: -3px; width: auto; min-width: 100%; font-size: 100%; clear: none; float: none; background-color: transparent; } .infobox-3cols-child { margin: auto; } .infobox .navbar { font-size: 100%; } /* T281642 */ body.skin-minerva .infobox-header, body.skin-minerva .infobox-subheader, body.skin-minerva .infobox-above, body.skin-minerva .infobox-title, body.skin-minerva .infobox-image, body.skin-minerva .infobox-full-data, body.skin-minerva .infobox-below { text-align: center; } /* Dark theme: [[William_Wragg]], [[Coral_Castle]] */ html.skin-theme-clientpref-night .infobox-full-data:not(.notheme) > div:not(.notheme)[style] { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } @media (prefers-color-scheme: dark) { html.skin-theme-clientpref-os .infobox-full-data:not(.notheme) div:not(.notheme) { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } /* Since infobox is a table, many infobox templates take advantage of this to add columns and rows to the infobox itself rather than as part of a new table inside them. This class should be discouraged and removed on the long term, but allows us to at least identify these tables going forward Currently in use on: [[Module:Infobox3cols]] Fixes issue described in https://phabricator.wikimedia.org/F55300125 on Vector 2022. */ @media (min-width: 640px) { body.skin--responsive .infobox-table { display: table !important; } body.skin--responsive .infobox-table > caption { display: table-caption !important; } body.skin--responsive .infobox-table > tbody { display: table-row-group; } body.skin--responsive .infobox-table tr { display: table-row !important; } body.skin--responsive .infobox-table th, body.skin--responsive .infobox-table td { padding-left: inherit; padding-right: inherit; } } 5scbs9zau8glf2g6gia4w61ymkkgomb 803588 803587 2024-07-31T23:21:15Z en>Jon (WMF) 0 Fixes [[phab:T369874]] via automated script 803588 sanitized-css text/css /* {{pp|small=y}} */ /* * This TemplateStyles sheet deliberately does NOT include the full set of * infobox styles. We are still working to migrate all of the manual * infoboxes. See [[MediaWiki talk:Common.css/to do#Infobox]] * DO NOT ADD THEM HERE */ /* * not strictly certain these styles are necessary since the modules now * exclusively output infobox-subbox or infobox, not both * just replicating the module faithfully */ .infobox-subbox { padding: 0; border: none; margin: -3px; width: auto; min-width: 100%; font-size: 100%; clear: none; float: none; background-color: transparent; } .infobox-3cols-child { margin: auto; } .infobox .navbar { font-size: 100%; } /* T281642 */ body.skin-minerva .infobox-header, body.skin-minerva .infobox-subheader, body.skin-minerva .infobox-above, body.skin-minerva .infobox-title, body.skin-minerva .infobox-image, body.skin-minerva .infobox-full-data, body.skin-minerva .infobox-below { text-align: center; } /* Dark theme: [[William_Wragg]], [[Coral_Castle]] */ @media screen { html.skin-theme-clientpref-night .infobox-full-data:not(.notheme) > div:not(.notheme)[style] { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } @media screen and ( prefers-color-scheme: dark) { html.skin-theme-clientpref-os .infobox-full-data:not(.notheme) div:not(.notheme) { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } /* Since infobox is a table, many infobox templates take advantage of this to add columns and rows to the infobox itself rather than as part of a new table inside them. This class should be discouraged and removed on the long term, but allows us to at least identify these tables going forward Currently in use on: [[Module:Infobox3cols]] Fixes issue described in https://phabricator.wikimedia.org/F55300125 on Vector 2022. */ @media (min-width: 640px) { body.skin--responsive .infobox-table { display: table !important; } body.skin--responsive .infobox-table > caption { display: table-caption !important; } body.skin--responsive .infobox-table > tbody { display: table-row-group; } body.skin--responsive .infobox-table tr { display: table-row !important; } body.skin--responsive .infobox-table th, body.skin--responsive .infobox-table td { padding-left: inherit; padding-right: inherit; } } 7pwddboq2hsp05urboa0k08jc5go6da 803589 803588 2024-11-12T17:39:02Z en>Izno 0 no reason for that 803589 sanitized-css text/css /* {{pp|small=y}} */ /* * This TemplateStyles sheet deliberately does NOT include the full set of * infobox styles. We are still working to migrate all of the manual * infoboxes. See [[MediaWiki talk:Common.css/to do#Infobox]] * DO NOT ADD THEM HERE */ /* * not strictly certain these styles are necessary since the modules now * exclusively output infobox-subbox or infobox, not both * just replicating the module faithfully */ .infobox-subbox { padding: 0; border: none; margin: -3px; width: auto; min-width: 100%; font-size: 100%; clear: none; float: none; background-color: transparent; } .infobox-3cols-child { margin: auto; } .infobox .navbar { font-size: 100%; } /* T281642 */ .infobox-header, .infobox-subheader, .infobox-above, .infobox-title, .infobox-image, .infobox-full-data, .infobox-below { text-align: center; } /* Dark theme: [[William_Wragg]], [[Coral_Castle]] */ @media screen { html.skin-theme-clientpref-night .infobox-full-data:not(.notheme) > div:not(.notheme)[style] { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } @media screen and ( prefers-color-scheme: dark) { html.skin-theme-clientpref-os .infobox-full-data:not(.notheme) div:not(.notheme) { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } /* Since infobox is a table, many infobox templates take advantage of this to add columns and rows to the infobox itself rather than as part of a new table inside them. This class should be discouraged and removed on the long term, but allows us to at least identify these tables going forward Currently in use on: [[Module:Infobox3cols]] Fixes issue described in https://phabricator.wikimedia.org/F55300125 on Vector 2022. */ @media (min-width: 640px) { body.skin--responsive .infobox-table { display: table !important; } body.skin--responsive .infobox-table > caption { display: table-caption !important; } body.skin--responsive .infobox-table > tbody { display: table-row-group; } body.skin--responsive .infobox-table tr { display: table-row !important; } body.skin--responsive .infobox-table th, body.skin--responsive .infobox-table td { padding-left: inherit; padding-right: inherit; } } 9u09w99811h4o10ugyqx5jzhofifgjq 803590 803589 2024-11-12T17:45:34Z en>Izno 0 remove this block entirely, common.css and minerva.css have the relevant styles 803590 sanitized-css text/css /* {{pp|small=y}} */ /* * This TemplateStyles sheet deliberately does NOT include the full set of * infobox styles. We are still working to migrate all of the manual * infoboxes. See [[MediaWiki talk:Common.css/to do#Infobox]] * DO NOT ADD THEM HERE */ /* * not strictly certain these styles are necessary since the modules now * exclusively output infobox-subbox or infobox, not both * just replicating the module faithfully */ .infobox-subbox { padding: 0; border: none; margin: -3px; width: auto; min-width: 100%; font-size: 100%; clear: none; float: none; background-color: transparent; } .infobox-3cols-child { margin: auto; } .infobox .navbar { font-size: 100%; } /* Dark theme: [[William_Wragg]], [[Coral_Castle]] */ @media screen { html.skin-theme-clientpref-night .infobox-full-data:not(.notheme) > div:not(.notheme)[style] { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } @media screen and ( prefers-color-scheme: dark) { html.skin-theme-clientpref-os .infobox-full-data:not(.notheme) div:not(.notheme) { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } /* Since infobox is a table, many infobox templates take advantage of this to add columns and rows to the infobox itself rather than as part of a new table inside them. This class should be discouraged and removed on the long term, but allows us to at least identify these tables going forward Currently in use on: [[Module:Infobox3cols]] Fixes issue described in https://phabricator.wikimedia.org/F55300125 on Vector 2022. */ @media (min-width: 640px) { body.skin--responsive .infobox-table { display: table !important; } body.skin--responsive .infobox-table > caption { display: table-caption !important; } body.skin--responsive .infobox-table > tbody { display: table-row-group; } body.skin--responsive .infobox-table tr { display: table-row !important; } body.skin--responsive .infobox-table th, body.skin--responsive .infobox-table td { padding-left: inherit; padding-right: inherit; } } 2pxfxg7rwc3jv0w8wyi0wqelqle32m2 803591 803590 2025-05-08T15:43:01Z en>Izno 0 remove .infobox-table tr rule per [[Template talk:Infobox#Display for table rows]] 803591 sanitized-css text/css /* {{pp|small=y}} */ /* * This TemplateStyles sheet deliberately does NOT include the full set of * infobox styles. We are still working to migrate all of the manual * infoboxes. See [[MediaWiki talk:Common.css/to do#Infobox]] * DO NOT ADD THEM HERE */ /* * not strictly certain these styles are necessary since the modules now * exclusively output infobox-subbox or infobox, not both * just replicating the module faithfully */ .infobox-subbox { padding: 0; border: none; margin: -3px; width: auto; min-width: 100%; font-size: 100%; clear: none; float: none; background-color: transparent; } .infobox-3cols-child { margin: auto; } .infobox .navbar { font-size: 100%; } /* Dark theme: [[William_Wragg]], [[Coral_Castle]] */ @media screen { html.skin-theme-clientpref-night .infobox-full-data:not(.notheme) > div:not(.notheme)[style] { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } @media screen and ( prefers-color-scheme: dark) { html.skin-theme-clientpref-os .infobox-full-data:not(.notheme) div:not(.notheme) { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } /* Since infobox is a table, many infobox templates take advantage of this to * add columns and rows to the infobox itself rather than as part of a new table * inside them. This class should be discouraged and removed on the long term, * but allows us to at least identify these tables going forward * Currently in use on: [[Module:Infobox3cols]] * Fixes issue described in [[phab:F55300125]] on Vector 2022. */ @media (min-width: 640px) { body.skin--responsive .infobox-table { display: table !important; } body.skin--responsive .infobox-table > caption { display: table-caption !important; } body.skin--responsive .infobox-table > tbody { display: table-row-group; } body.skin--responsive .infobox-table th, body.skin--responsive .infobox-table td { padding-left: inherit; padding-right: inherit; } } 0drczzpwouuzdu6lmdtibxmcwzo6l44 803592 803591 2025-06-16T15:26:13Z en>Izno 0 per talk page 803592 sanitized-css text/css /* {{pp|small=y}} */ /* * This TemplateStyles sheet deliberately does NOT include the full set of * infobox styles. We are still working to migrate all of the manual * infoboxes. See [[MediaWiki talk:Common.css/to do#Infobox]] * DO NOT ADD THEM HERE */ /* * not strictly certain these styles are necessary since the modules now * exclusively output infobox-subbox or infobox, not both * just replicating the module faithfully */ .infobox-subbox { padding: 0; border: none; margin: -3px; width: auto; min-width: 100%; font-size: 100%; clear: none; float: none; background-color: transparent; } .infobox-3cols-child { margin: auto; } .infobox .navbar { font-size: 100%; } /* Dark theme: [[William_Wragg]], [[Coral_Castle]] */ @media screen { html.skin-theme-clientpref-night .infobox-full-data:not(.notheme) > div:not(.notheme)[style] { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } @media screen and ( prefers-color-scheme: dark) { html.skin-theme-clientpref-os .infobox-full-data:not(.notheme) > div:not(.notheme)[style] { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } /* Since infobox is a table, many infobox templates take advantage of this to * add columns and rows to the infobox itself rather than as part of a new table * inside them. This class should be discouraged and removed on the long term, * but allows us to at least identify these tables going forward * Currently in use on: [[Module:Infobox3cols]] * Fixes issue described in [[phab:F55300125]] on Vector 2022. */ @media (min-width: 640px) { body.skin--responsive .infobox-table { display: table !important; } body.skin--responsive .infobox-table > caption { display: table-caption !important; } body.skin--responsive .infobox-table > tbody { display: table-row-group; } body.skin--responsive .infobox-table th, body.skin--responsive .infobox-table td { padding-left: inherit; padding-right: inherit; } } ed9hwgdet8h6h2bc3c9iiv47kgu74p6 803593 803592 2025-10-06T00:12:24Z en>Jonesey95 0 fix text color in dark mode; this generally works but may cause problems, in which case revert or try specifying a different CSS value. background-color: transparent is generally not advised, but if it is necessary, it needs color:inherit. 803593 sanitized-css text/css /* {{pp|small=y}} */ /* * This TemplateStyles sheet deliberately does NOT include the full set of * infobox styles. We are still working to migrate all of the manual * infoboxes. See [[MediaWiki talk:Common.css/to do#Infobox]] * DO NOT ADD THEM HERE */ /* * not strictly certain these styles are necessary since the modules now * exclusively output infobox-subbox or infobox, not both * just replicating the module faithfully */ .infobox-subbox { padding: 0; border: none; margin: -3px; width: auto; min-width: 100%; font-size: 100%; clear: none; float: none; background-color: transparent; color:inherit; } .infobox-3cols-child { margin: auto; } .infobox .navbar { font-size: 100%; } /* Dark theme: [[William_Wragg]], [[Coral_Castle]] */ @media screen { html.skin-theme-clientpref-night .infobox-full-data:not(.notheme) > div:not(.notheme)[style] { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } @media screen and ( prefers-color-scheme: dark) { html.skin-theme-clientpref-os .infobox-full-data:not(.notheme) > div:not(.notheme)[style] { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } /* Since infobox is a table, many infobox templates take advantage of this to * add columns and rows to the infobox itself rather than as part of a new table * inside them. This class should be discouraged and removed on the long term, * but allows us to at least identify these tables going forward * Currently in use on: [[Module:Infobox3cols]] * Fixes issue described in [[phab:F55300125]] on Vector 2022. */ @media (min-width: 640px) { body.skin--responsive .infobox-table { display: table !important; } body.skin--responsive .infobox-table > caption { display: table-caption !important; } body.skin--responsive .infobox-table > tbody { display: table-row-group; } body.skin--responsive .infobox-table th, body.skin--responsive .infobox-table td { padding-left: inherit; padding-right: inherit; } } 5h4nxyw7y2buszwy42v81sw59h2ytd1 803594 803593 2025-10-10T07:08:27Z en>Zackmann08 0 attempting to resolve a nesting issue. 803594 sanitized-css text/css /* {{pp|small=y}} */ /* * This TemplateStyles sheet deliberately does NOT include the full set of * infobox styles. We are still working to migrate all of the manual * infoboxes. See [[MediaWiki talk:Common.css/to do#Infobox]] * DO NOT ADD THEM HERE */ /* * not strictly certain these styles are necessary since the modules now * exclusively output infobox-subbox or infobox, not both * just replicating the module faithfully */ .infobox-subbox { padding: 0; border: none; margin: -3px; width: auto; min-width: 100%; font-size: 100%; clear: none; float: none; background-color: transparent; color:inherit; } .infobox-3cols-child { margin: -3px; } .infobox .navbar { font-size: 100%; } /* Dark theme: [[William_Wragg]], [[Coral_Castle]] */ @media screen { html.skin-theme-clientpref-night .infobox-full-data:not(.notheme) > div:not(.notheme)[style] { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } @media screen and ( prefers-color-scheme: dark) { html.skin-theme-clientpref-os .infobox-full-data:not(.notheme) > div:not(.notheme)[style] { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } /* Since infobox is a table, many infobox templates take advantage of this to * add columns and rows to the infobox itself rather than as part of a new table * inside them. This class should be discouraged and removed on the long term, * but allows us to at least identify these tables going forward * Currently in use on: [[Module:Infobox3cols]] * Fixes issue described in [[phab:F55300125]] on Vector 2022. */ @media (min-width: 640px) { body.skin--responsive .infobox-table { display: table !important; } body.skin--responsive .infobox-table > caption { display: table-caption !important; } body.skin--responsive .infobox-table > tbody { display: table-row-group; } body.skin--responsive .infobox-table th, body.skin--responsive .infobox-table td { padding-left: inherit; padding-right: inherit; } } et19aj2zpnqpz3yxaypsrg4fv3vb8t6 803595 803594 2026-05-09T23:05:29Z en>Zackmann08 0 testing directly implementing [[Template:Longitem]] for infobox labels. 803595 sanitized-css text/css /* {{pp|small=y}} */ /* * This TemplateStyles sheet deliberately does NOT include the full set of * infobox styles. We are still working to migrate all of the manual * infoboxes. See [[MediaWiki talk:Common.css/to do#Infobox]] * DO NOT ADD THEM HERE */ /* * not strictly certain these styles are necessary since the modules now * exclusively output infobox-subbox or infobox, not both * just replicating the module faithfully */ .infobox-subbox { padding: 0; border: none; margin: -3px; width: auto; min-width: 100%; font-size: 100%; clear: none; float: none; background-color: transparent; color:inherit; } .infobox-3cols-child { margin: -3px; } .infobox .navbar { font-size: 100%; } /* Dark theme: [[William_Wragg]], [[Coral_Castle]] */ @media screen { html.skin-theme-clientpref-night .infobox-full-data:not(.notheme) > div:not(.notheme)[style] { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } @media screen and ( prefers-color-scheme: dark) { html.skin-theme-clientpref-os .infobox-full-data:not(.notheme) > div:not(.notheme)[style] { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } /* Directly implement [[Template:Longitem]] for infobox labels. */ th.infobox-label { display: inline-block; line-height: 1.2em; padding: .1em; } /* Since infobox is a table, many infobox templates take advantage of this to * add columns and rows to the infobox itself rather than as part of a new table * inside them. This class should be discouraged and removed on the long term, * but allows us to at least identify these tables going forward * Currently in use on: [[Module:Infobox3cols]] * Fixes issue described in [[phab:F55300125]] on Vector 2022. */ @media (min-width: 640px) { body.skin--responsive .infobox-table { display: table !important; } body.skin--responsive .infobox-table > caption { display: table-caption !important; } body.skin--responsive .infobox-table > tbody { display: table-row-group; } body.skin--responsive .infobox-table th, body.skin--responsive .infobox-table td { padding-left: inherit; padding-right: inherit; } } o2lmhj317m0wkh4z3i4jye0edyjtgo0 803596 803595 2026-05-10T02:18:05Z en>Zackmann08 0 Undid revision [[Special:Diff/1353378980|1353378980]] by [[Special:Contributions/Zackmann08|Zackmann08]] ([[User talk:Zackmann08|talk]]) possibly broke something... 803596 sanitized-css text/css /* {{pp|small=y}} */ /* * This TemplateStyles sheet deliberately does NOT include the full set of * infobox styles. We are still working to migrate all of the manual * infoboxes. See [[MediaWiki talk:Common.css/to do#Infobox]] * DO NOT ADD THEM HERE */ /* * not strictly certain these styles are necessary since the modules now * exclusively output infobox-subbox or infobox, not both * just replicating the module faithfully */ .infobox-subbox { padding: 0; border: none; margin: -3px; width: auto; min-width: 100%; font-size: 100%; clear: none; float: none; background-color: transparent; color:inherit; } .infobox-3cols-child { margin: -3px; } .infobox .navbar { font-size: 100%; } /* Dark theme: [[William_Wragg]], [[Coral_Castle]] */ @media screen { html.skin-theme-clientpref-night .infobox-full-data:not(.notheme) > div:not(.notheme)[style] { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } @media screen and ( prefers-color-scheme: dark) { html.skin-theme-clientpref-os .infobox-full-data:not(.notheme) > div:not(.notheme)[style] { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } /* Since infobox is a table, many infobox templates take advantage of this to * add columns and rows to the infobox itself rather than as part of a new table * inside them. This class should be discouraged and removed on the long term, * but allows us to at least identify these tables going forward * Currently in use on: [[Module:Infobox3cols]] * Fixes issue described in [[phab:F55300125]] on Vector 2022. */ @media (min-width: 640px) { body.skin--responsive .infobox-table { display: table !important; } body.skin--responsive .infobox-table > caption { display: table-caption !important; } body.skin--responsive .infobox-table > tbody { display: table-row-group; } body.skin--responsive .infobox-table th, body.skin--responsive .infobox-table td { padding-left: inherit; padding-right: inherit; } } et19aj2zpnqpz3yxaypsrg4fv3vb8t6 803597 803596 2026-06-05T20:49:48Z en>Krinkle 0 Add basic layout (float/clear/width/margin-left) so that in [[mw:Manual:Safemode|safemode]] most articles are at least decently readable. I am deliberately not yet deleting from [[MediaWiki:Common.css]] because still is stil used in countless places without this module, and therefore to avoid bloat, I'm not adding the rest of the colors and other more detailed styles. 803597 sanitized-css text/css /* {{pp|small=y}} */ /* * This TemplateStyles sheet deliberately does NOT include the full set of * infobox styles. We are still working to migrate all of the manual * infoboxes. See [[MediaWiki talk:Common.css/to do#Infobox]] * DO NOT ADD THEM HERE */ /* NOTE: This is maintained both here and in [[MediaWiki:Common.css]] until migration is complete. * Starting with bare minimum for the benefit of [[mw:mManual:Safemode]]. */ @media (min-width: 640px) { .infobox { /* @noflip */ margin-left: 1em; /* @noflip */ float: right; /* @noflip */ clear: right; width: 22em; } } /* * not strictly certain these styles are necessary since the modules now * exclusively output infobox-subbox or infobox, not both * just replicating the module faithfully */ .infobox-subbox { padding: 0; border: none; margin: -3px; width: auto; min-width: 100%; font-size: 100%; clear: none; float: none; background-color: transparent; color:inherit; } .infobox-3cols-child { margin: -3px; } .infobox .navbar { font-size: 100%; } /* Dark theme: [[William_Wragg]], [[Coral_Castle]] */ @media screen { html.skin-theme-clientpref-night .infobox-full-data:not(.notheme) > div:not(.notheme)[style] { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } @media screen and ( prefers-color-scheme: dark) { html.skin-theme-clientpref-os .infobox-full-data:not(.notheme) > div:not(.notheme)[style] { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } /* Since infobox is a table, many infobox templates take advantage of this to * add columns and rows to the infobox itself rather than as part of a new table * inside them. This class should be discouraged and removed on the long term, * but allows us to at least identify these tables going forward * Currently in use on: [[Module:Infobox3cols]] * Fixes issue described in [[phab:F55300125]] on Vector 2022. */ @media (min-width: 640px) { body.skin--responsive .infobox-table { display: table !important; } body.skin--responsive .infobox-table > caption { display: table-caption !important; } body.skin--responsive .infobox-table > tbody { display: table-row-group; } body.skin--responsive .infobox-table th, body.skin--responsive .infobox-table td { padding-left: inherit; padding-right: inherit; } } j2wwjoocxn9i57ypq18ttmt9bzysi82 803598 803597 2026-06-05T20:49:55Z en>Krinkle 0 803598 sanitized-css text/css /* {{pp|small=y}} */ /* * This TemplateStyles sheet deliberately does NOT include the full set of * infobox styles. We are still working to migrate all of the manual * infoboxes. See [[MediaWiki talk:Common.css/to do#Infobox]] * DO NOT ADD THEM HERE */ /* NOTE: This is maintained both here and in [[MediaWiki:Common.css]] until migration is complete. * Starting with bare minimum for the benefit of [[mw:Manual:Safemode]]. */ @media (min-width: 640px) { .infobox { /* @noflip */ margin-left: 1em; /* @noflip */ float: right; /* @noflip */ clear: right; width: 22em; } } /* * not strictly certain these styles are necessary since the modules now * exclusively output infobox-subbox or infobox, not both * just replicating the module faithfully */ .infobox-subbox { padding: 0; border: none; margin: -3px; width: auto; min-width: 100%; font-size: 100%; clear: none; float: none; background-color: transparent; color:inherit; } .infobox-3cols-child { margin: -3px; } .infobox .navbar { font-size: 100%; } /* Dark theme: [[William_Wragg]], [[Coral_Castle]] */ @media screen { html.skin-theme-clientpref-night .infobox-full-data:not(.notheme) > div:not(.notheme)[style] { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } @media screen and ( prefers-color-scheme: dark) { html.skin-theme-clientpref-os .infobox-full-data:not(.notheme) > div:not(.notheme)[style] { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } /* Since infobox is a table, many infobox templates take advantage of this to * add columns and rows to the infobox itself rather than as part of a new table * inside them. This class should be discouraged and removed on the long term, * but allows us to at least identify these tables going forward * Currently in use on: [[Module:Infobox3cols]] * Fixes issue described in [[phab:F55300125]] on Vector 2022. */ @media (min-width: 640px) { body.skin--responsive .infobox-table { display: table !important; } body.skin--responsive .infobox-table > caption { display: table-caption !important; } body.skin--responsive .infobox-table > tbody { display: table-row-group; } body.skin--responsive .infobox-table th, body.skin--responsive .infobox-table td { padding-left: inherit; padding-right: inherit; } } 1cfxsygzesgd9qp9tomkwlfeh7sk09g 803599 803598 2026-06-08T00:49:01Z en>Zackmann08 0 Implementing new class for hiding extra rows. 803599 sanitized-css text/css /* {{pp|small=y}} */ /* * This TemplateStyles sheet deliberately does NOT include the full set of * infobox styles. We are still working to migrate all of the manual * infoboxes. See [[MediaWiki talk:Common.css/to do#Infobox]] * DO NOT ADD THEM HERE */ /* NOTE: This is maintained both here and in [[MediaWiki:Common.css]] until migration is complete. * Starting with bare minimum for the benefit of [[mw:Manual:Safemode]]. */ @media (min-width: 640px) { .infobox { /* @noflip */ margin-left: 1em; /* @noflip */ float: right; /* @noflip */ clear: right; width: 22em; } } /* * not strictly certain these styles are necessary since the modules now * exclusively output infobox-subbox or infobox, not both * just replicating the module faithfully */ .infobox-subbox { padding: 0; border: none; margin: -3px; width: auto; min-width: 100%; font-size: 100%; clear: none; float: none; background-color: transparent; color:inherit; } .infobox-3cols-child { margin: -3px; } .infobox .navbar { font-size: 100%; } .infobox-hiddenrow { display: none; } /* Dark theme: [[William_Wragg]], [[Coral_Castle]] */ @media screen { html.skin-theme-clientpref-night .infobox-full-data:not(.notheme) > div:not(.notheme)[style] { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } @media screen and ( prefers-color-scheme: dark) { html.skin-theme-clientpref-os .infobox-full-data:not(.notheme) > div:not(.notheme)[style] { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } /* Since infobox is a table, many infobox templates take advantage of this to * add columns and rows to the infobox itself rather than as part of a new table * inside them. This class should be discouraged and removed on the long term, * but allows us to at least identify these tables going forward * Currently in use on: [[Module:Infobox3cols]] * Fixes issue described in [[phab:F55300125]] on Vector 2022. */ @media (min-width: 640px) { body.skin--responsive .infobox-table { display: table !important; } body.skin--responsive .infobox-table > caption { display: table-caption !important; } body.skin--responsive .infobox-table > tbody { display: table-row-group; } body.skin--responsive .infobox-table th, body.skin--responsive .infobox-table td { padding-left: inherit; padding-right: inherit; } } qlld126pj5n6tifvwjuvk0jxst79dv3 803600 803599 2026-06-09T21:49:07Z en>Izno 0 sigh 803600 sanitized-css text/css /* {{pp|small=y}} */ /* * This TemplateStyles sheet deliberately does NOT include the full set of * infobox styles. We are still working to migrate all of the manual * infoboxes. See [[MediaWiki talk:Common.css/to do#Infobox]] * DO NOT ADD THEM HERE */ /* NOTE: This is maintained both here and in [[MediaWiki:Common.css]] until migration is complete. * Starting with bare minimum for the benefit of [[mw:Manual:Safemode]]. */ @media (min-width: 640px) { .infobox { /* @noflip */ margin-left: 1em; /* @noflip */ float: right; /* @noflip */ clear: right; width: 22em; } } /* * not strictly certain these styles are necessary since the modules now * exclusively output infobox-subbox or infobox, not both * just replicating the module faithfully */ .infobox-subbox { padding: 0; border: none; margin: -3px; width: auto; min-width: 100%; font-size: 100%; clear: none; float: none; background-color: transparent; color:inherit; } .infobox-3cols-child { margin: -3px; } .infobox .navbar { font-size: 100%; } /* remove when infobox is not a table anymore */ .infobox-hiddenrow, /* we mean it, Minerva. but also Vector 2022 in the future at some point */ body.skin--responsive.skin--responsive .infobox .infobox-hiddenrow { display: none; } /* Dark theme: [[William_Wragg]], [[Coral_Castle]] */ @media screen { html.skin-theme-clientpref-night .infobox-full-data:not(.notheme) > div:not(.notheme)[style] { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } @media screen and (prefers-color-scheme: dark) { html.skin-theme-clientpref-os .infobox-full-data:not(.notheme) > div:not(.notheme)[style] { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } /* Since infobox is a table, many infobox templates take advantage of this to * add columns and rows to the infobox itself rather than as part of a new table * inside them. This class should be discouraged and removed on the long term, * but allows us to at least identify these tables going forward * Currently in use on: [[Module:Infobox3cols]] * Fixes issue described in [[phab:F55300125]] on Vector 2022. */ @media (min-width: 640px) { body.skin--responsive .infobox-table { display: table !important; } body.skin--responsive .infobox-table > caption { display: table-caption !important; } body.skin--responsive .infobox-table > tbody { display: table-row-group; } body.skin--responsive .infobox-table th, body.skin--responsive .infobox-table td { padding-left: inherit; padding-right: inherit; } } rl8j583dyum0u1mdd4ip8cx64nsci33 803601 803600 2026-06-09T21:49:34Z en>Izno 0 spaceeeee 803601 sanitized-css text/css /* {{pp|small=y}} */ /* * This TemplateStyles sheet deliberately does NOT include the full set of * infobox styles. We are still working to migrate all of the manual * infoboxes. See [[MediaWiki talk:Common.css/to do#Infobox]] * DO NOT ADD THEM HERE */ /* NOTE: This is maintained both here and in [[MediaWiki:Common.css]] until migration is complete. * Starting with bare minimum for the benefit of [[mw:Manual:Safemode]]. */ @media (min-width: 640px) { .infobox { /* @noflip */ margin-left: 1em; /* @noflip */ float: right; /* @noflip */ clear: right; width: 22em; } } /* * not strictly certain these styles are necessary since the modules now * exclusively output infobox-subbox or infobox, not both * just replicating the module faithfully */ .infobox-subbox { padding: 0; border: none; margin: -3px; width: auto; min-width: 100%; font-size: 100%; clear: none; float: none; background-color: transparent; color:inherit; } .infobox-3cols-child { margin: -3px; } .infobox .navbar { font-size: 100%; } /* remove when infobox is not a table anymore */ .infobox-hiddenrow, /* we mean it, Minerva. but also Vector 2022 in the future at some point */ body.skin--responsive.skin--responsive .infobox .infobox-hiddenrow { display: none; } /* Dark theme: [[William Wragg]], [[Coral Castle]] */ @media screen { html.skin-theme-clientpref-night .infobox-full-data:not(.notheme) > div:not(.notheme)[style] { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } @media screen and (prefers-color-scheme: dark) { html.skin-theme-clientpref-os .infobox-full-data:not(.notheme) > div:not(.notheme)[style] { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } /* Since infobox is a table, many infobox templates take advantage of this to * add columns and rows to the infobox itself rather than as part of a new table * inside them. This class should be discouraged and removed on the long term, * but allows us to at least identify these tables going forward * Currently in use on: [[Module:Infobox3cols]] * Fixes issue described in [[phab:F55300125]] on Vector 2022. */ @media (min-width: 640px) { body.skin--responsive .infobox-table { display: table !important; } body.skin--responsive .infobox-table > caption { display: table-caption !important; } body.skin--responsive .infobox-table > tbody { display: table-row-group; } body.skin--responsive .infobox-table th, body.skin--responsive .infobox-table td { padding-left: inherit; padding-right: inherit; } } bi1nsztkx4350a55cuzjhaotvp5h216 803602 803601 2026-07-30T00:18:39Z SM7 3953 27 revisions imported from [[:en:Module:Infobox/styles.css]] 803601 sanitized-css text/css /* {{pp|small=y}} */ /* * This TemplateStyles sheet deliberately does NOT include the full set of * infobox styles. We are still working to migrate all of the manual * infoboxes. See [[MediaWiki talk:Common.css/to do#Infobox]] * DO NOT ADD THEM HERE */ /* NOTE: This is maintained both here and in [[MediaWiki:Common.css]] until migration is complete. * Starting with bare minimum for the benefit of [[mw:Manual:Safemode]]. */ @media (min-width: 640px) { .infobox { /* @noflip */ margin-left: 1em; /* @noflip */ float: right; /* @noflip */ clear: right; width: 22em; } } /* * not strictly certain these styles are necessary since the modules now * exclusively output infobox-subbox or infobox, not both * just replicating the module faithfully */ .infobox-subbox { padding: 0; border: none; margin: -3px; width: auto; min-width: 100%; font-size: 100%; clear: none; float: none; background-color: transparent; color:inherit; } .infobox-3cols-child { margin: -3px; } .infobox .navbar { font-size: 100%; } /* remove when infobox is not a table anymore */ .infobox-hiddenrow, /* we mean it, Minerva. but also Vector 2022 in the future at some point */ body.skin--responsive.skin--responsive .infobox .infobox-hiddenrow { display: none; } /* Dark theme: [[William Wragg]], [[Coral Castle]] */ @media screen { html.skin-theme-clientpref-night .infobox-full-data:not(.notheme) > div:not(.notheme)[style] { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } @media screen and (prefers-color-scheme: dark) { html.skin-theme-clientpref-os .infobox-full-data:not(.notheme) > div:not(.notheme)[style] { background: #1f1f23 !important; /* switch with var( --color-base ) when supported. */ color: #f8f9fa; } } /* Since infobox is a table, many infobox templates take advantage of this to * add columns and rows to the infobox itself rather than as part of a new table * inside them. This class should be discouraged and removed on the long term, * but allows us to at least identify these tables going forward * Currently in use on: [[Module:Infobox3cols]] * Fixes issue described in [[phab:F55300125]] on Vector 2022. */ @media (min-width: 640px) { body.skin--responsive .infobox-table { display: table !important; } body.skin--responsive .infobox-table > caption { display: table-caption !important; } body.skin--responsive .infobox-table > tbody { display: table-row-group; } body.skin--responsive .infobox-table th, body.skin--responsive .infobox-table td { padding-left: inherit; padding-right: inherit; } } bi1nsztkx4350a55cuzjhaotvp5h216 शीला दीक्षित 0 88968 803301 763125 2026-07-29T16:06:56Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803301 wikitext text/x-wiki {{Infobox officeholder | name = शीला दीक्षित | image = Sheila Dikshit Ji.jpg | caption = | office1 = प्रदेश कांग्रेस कमेटी, दिल्ली | term_start1 = 11 जनवरी 2019 | term_end1 = 20 जुलाई 2019 | 1blankname1 = राष्ट्रीय अध्यक्ष (कांग्रेस) | 1namedata1 = [[राहुल गांधी]] | predecessor1 =अजय मांकड़ | office2 = केरल के 20वीं राज्यपाल | 1blankname2 = मुख्यमंत्री | 1namedata2 = ओमान चांडी | predecessor2 = निखिल कुमार | successor2 = पी. सतशिवम<ref>{{cite web | title=Sathasivam becomes Kerala governor, to take oath on September 5 | website=India Today | date=3 September 2014 | url=http://indiatoday.intoday.in/story/sathasivam-kerala-governor-cji-home-ministry-supreme-court/1/380730.html | access-date=29 January 2016}}</ref> | term_start2 = 11 मार्च 2014 | term_end2 = 4 सितंबर 2014 <!-- -->| office3 =दिल्ली के 6वीं मुख्यमंत्री | predecessor3 = [[सुषमा स्वराज]] | successor3 = [[अरविंद केजरीवाल]] | lieutenant_governor3 = | term_start3 = 4 दिसंबर 1998 | term_end3 = 27 दिसंबर 2013 <!-- -->| office4 = दिल्ली बिधानसभा बिधायक | predecessor5 = कीर्ति आजाद | successor5 = ''समाप्त'' | constituency5 = गोल मार्केट | term_start5 = 3 दिसंबर 1998 | term_end5 = 3 दिसंबर 2008 <!-- -->| office5 = | predecessor4 = ''स्थाप.'' | successor4 = [[अरविंद केजरीवाल]] | constituency4 = नई दिल्ली | term_start4 = 4 दिसंबर 2008 | term_end4 = 28 दिसंबर 2013 <!-- -->| office6 = [[सांसद, लोकसभा]] | constituency6 = कन्नौज | predecessor6 = छोटे सिंह यादव | successor6 = छोटे सिंह यादव | term_start6 = 1984 | term_end6 = 1989 <!-- -->| office7 =भारतीय डेलिगेशन के सदस्य <br /> यूनाइटेड नेशंस कमीशन ऑन वूमन अफेयर्स | primeminister7 = [[इंदिरा गांधी]] <br /> [[राजीव गांधी]] | term_start7 = 1984 | term_end7 = 89 <!-- -->| office8 = संसदीय मामिला के राज्यमंत्री | primeminister8 = [[राजीव गांधी]] | term_start8 = 1984 | term_end8 = 1989 <!-- -->| party = [[भारतीय राष्ट्रीय कांग्रेस]] | birth_date = {{Birth date|df=y|1938|3|31}} | birth_place = [[कपूरथला]], [[कपूरथला राज]], [[ब्रिटिश भारत]] <br/> (आज्काल्ह: [[पंजाब]], [[भारत]]) | spouse = विनोद दीक्षित | children = 2 | alma_mater = [[मिरांडा हाउस]], [[दिल्ली युनिवर्सिटी]] | website = | death_date = {{Death date and age|df=yes|2019|7|20|1938|3|31}}<ref>{{cite web |author=News Galiyara |url=https://newsgaliyara.com/three-time-delhis-chief-minister-sheila-dikshit-passed-away-at-81/ |title=Three-Time Delhi's Chief Minister Sheila Dikshit passed away at 81 |publisher=NewsGaliyara.com |date=20 July 2019 |access-date=20 July 2019 |archive-date=20 July 2019 |archive-url=https://web.archive.org/web/20190720154045/https://newsgaliyara.com/three-time-delhis-chief-minister-sheila-dikshit-passed-away-at-81/ |url-status=dead }}</ref> | death_place = [[नई दिल्ली]], [[भारत]] | successor1 =सुभाष चोपड़ा | prior_term1 = }} '''शीला दीक्षित''' (31 मार्च 1938 — 20 जुलाई 2019) एगो भारतीय राजनीतिज्ञ रहली जे 1998 से 2013 ले दिल्ली के मुख्यमंत्री के रूप में काम कइली। एकरे अलावा, 11 मार्च 2014 से 25 अगस्त 2014 ले [[भारत]] के [[केरल]] राज्य के गवर्नर,<ref name="nbt-4mar14">{{Cite web|url=http://hindi.economictimes.indiatimes.com/delhi/politics/Former-Delhi-chief-minister-Sheila-Dikshit-is-new-governor-of-Kerala/articleshow/31422363.cms|title=शीला दीक्षित को केरल का राज्यपाल बनाया गया|date=4 मार्च 2014|publisher=नवभारत टाईम्स|access-date=5 मार्च 2014}}{{Dead link|date=September 2023 |bot=InternetArchiveBot |fix-attempted=yes }}</ref><ref name="pib-5mar14">{{Cite web|url=http://pib.nic.in/newsite/hindirelease.aspx?relid=27331|title=राज्‍यपालों की नियुक्ति|date=5 मार्च 2014|publisher=पत्र सूचना कार्यालय, भारत सरकार|archive-url=https://web.archive.org/web/20150530224428/http://pib.nic.in/newsite/hindirelease.aspx?relid=27331|archive-date=30 मई 2015|access-date=5 मार्च 2014}}</ref> 1984 से 1989 ले कन्नौज से संसद के सदस्य, आ 10 जनवरी 2019 से 10 जनवरी 2019 ले दिल्ली प्रदेश कांग्रेस कमेटी के अध्यक्ष रहलीं ओकर मौत हो गइल. दीक्षित 15 साल तक [[दिल्ली]] के [[राष्ट्रिय राजधानी क्षेत्र (भारत)|राष्ट्रीय राजधानी क्षेत्र]] राज्य के [[मुख्यमंत्री]] रहले। ऊ दिल्ली के दूसरी महिला मुख्यमंत्री रहली, आ देश के पहिली महिला मुख्यमंत्री रहली जे लगातार तीन बेर मुख्यमंत्री के पद पर रहली। 17 दिसंबर 2008 के लगातार तीसरा बेर [[दिल्ली]] विधान सभा खातिर चुनल गइलें। साल 2013 के विधानसभा चुनाव में कांग्रेस के हार के बाद उनुका अपना पद से इस्तीफा देवे के पड़ल। केरल के राज्यपाल श्री निखिल कुमार के इस्तीफा के बाद उनुका के एह पद प नियुक्त कइल गइल रहे, हालांकि उ खुद 25 अगस्त के ए पद से इस्तीफा दे देले रहले। साल 2017 के उत्तर प्रदेश विधानसभा चुनाव में उनुका के कांग्रेस पार्टी के मुख्यमंत्री पद के उम्मीदवार घोषित कइल गइल रहे, लेकिन बाद में उनुकर नाम वापस ले लेले। एकरा बाद 10 जनवरी 2019 के उनुका के दिल्ली प्रदेश कांग्रेस समिति के अध्यक्ष घोषित कइल गइल . 20 जुलाई 2019 शनिवार के दिल्ली के एस्कॉर्ट अस्पताल में उनुकर निधन हो गइल। {{clear}} == संदर्भ == {{Reflist|33em}} [[श्रेणी:1938 में जनम]] [[श्रेणी:2019 में निधन]] {{India-politician-stub}} rs9b967bedztkax1f9a67l5mdyk9l1s भारतीय युवा कांग्रेस 0 88979 803316 766278 2026-07-29T16:20:24Z SM7Bot 7808 /* top */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803316 wikitext text/x-wiki {{Infobox political party | name = भारतीय युवा कांग्रेस | native_name = | logo = [[File:Indian Youth Congress Logo.jpg|200px]] | colorcode = DeepSkyBlue | president = श्रीनिवास बी वी | secretary = | spokesperson = अमरीश रंजन पांडे | foundation = १९६० | headquarters = [[नई दिल्ली]] | international = | website = {{url|https://iyc.in/}} | dissolved = | membership = २ करोड़<ref>https://m.hindustantimes.com/india-news/aicc-asks-youth-wing-to-target-230-lok-sabha-seats/story-96BpzQRZMs8nWhRpOJ3fdN_amp.html</ref> }} <references /> '''भारतीय युवा कांग्रेस''' इ [[भारतीय राष्ट्रीय काँग्रेस|भारतीय राष्ट्रीय कांग्रेस]] के युवा प्रकोष्ठ ह। भारतीय युवा कांग्रेस १९४७ में भारत के बंटवारा के ठीक बाद के समय से लेके १९६० के दशक के अंत तक भारतीय राष्ट्रीय कांग्रेस के एगो विंग रहे। जबकि प्रधानमंत्री, [[इंदिरा गाँधी|इंदिरा गांधी]] युवा कांग्रेस के कांग्रेस पार्टी के फ्रंटल संगठन के रूप में स्थापित क के एगो नाया आयाम देली, जवना के मकसद रहे कि सामाजिक काम कइल अउरी दक्षिणपंथी दल के खिलाफ मोर्चा खोलल जाए। प्रियरंजन दासमुंशी भारतीय युवा कांग्रेस के पहिला चुनल अध्यक्ष रहलें; बाद में भारतीय मंत्रिमंडल में सूचना आ प्रसारण आ संसदीय मामिला के मंत्री बनलें। 4sbnarrvyms62bfefc576am33dlzpzb चार्ल्स डी गॉल 0 88981 803365 766311 2026-07-29T16:23:57Z SM7Bot 7808 /* top */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803365 wikitext text/x-wiki {{Infobox officeholder | name = चार्ल्स डी गॉल | image = De Gaulle-OWI (cropped)-(c).jpg | caption = चार्ल्स डी गॉल १९४४ में | office = [[फ्रांस के रास्ट्रपति]] | predecessor = [[René Coty]] | primeminister = {{plainlist| * [[Michel Debré]] * [[Georges Pompidou]] * [[Maurice Couve de Murville]]}} | successor = [[Georges Pompidou]] | termstart = 8 January 1959 | termend = 28 April 1969 | office1 = [[Prime Minister of France]] | president1 = [[René Coty]] | termstart1 = 1 June 1958 | termend1 = 8 January 1959 | predecessor1 = [[Pierre Pflimlin]] | successor1 = [[Michel Debré]] | office2 = Chairman of the [[Provisional Government of the French Republic]] | termstart2 = 3 June 1944 | termend2 = 26 January 1946 | predecessor2 = {{ubl|[[Philippe Pétain]]<br />(Chief of the French State) |[[Pierre Laval]]<br />(Chief of the Government) }} | successor2 = [[Félix Gouin]] | birth_name = Charles André Joseph Marie de Gaulle | birth_date = {{birth date|1890|11|22|df=y}} | birth_place = [[Lille]], France | death_date = {{death date and age|1970|11|9|1890|11|22|df=y}} | death_place = [[Colombey-les-Deux-Églises]], [[France]] | resting_place = [[Colombey-les-Deux-Églises]], [[France]] | party = [[Union of Democrats for the Republic]] (1967–1969) | otherparty = [[Union for the New Republic (France)|Union for the New Republic]] (1958–1967) | spouse = {{marriage|[[Yvonne de Gaulle|Yvonne Vendroux]]|1921}} | children = {{hlist|[[Philippe de Gaulle|Philippe]]|Élisabeth|[[Anne de Gaulle|Anne]]}} | alma_mater = [[École spéciale militaire de Saint-Cyr]] | signature = Charles de Gaulle Signature 2.svg | footnotes = {{notelist}} | allegiance = {{plainlist| * [[French Third Republic]] * [[Free France]]}} | branch = {{plainlist| * [[French Army]] * [[French Armed Forces]] * [[Free French Forces]]}} | serviceyears = 1912–1944 | rank = [[Brigadier general]] | unit = {{plainlist| * Infantry * Armoured cavalry}} | commands = {{plainlist| * [[4e Division cuirassée]] * [[Free France]]}} | battles = {{Tree list}} * [[World War I]] ** [[Battle of Verdun]] ** [[Battle of the Somme]] * [[Polish–Soviet War]] * [[World War II]] ** [[Battle of France]] ** [[Battle of Montcornet]] ** [[Battle of Abbeville]] ** [[Battle of Dakar]] ** [[Liberation of Paris]] {{Tree list/end}} }}'''चार्ल्स आंद्रे जोसेफ मैरी डी गॉल''' ;<ref>{{Citation|last=Wells|first=John C.|year=2008|title=Longman Pronunciation Dictionary|edition=3rd|publisher=Longman|isbn=978-1-4058-8118-0}}</ref> आमतौर पर एकरा के संक्षिप्त रूप से '''सीडीजी''' कहल जाला आ फ्रांस में एकरा के खाली " ''ले जनरल'' ", "द जनरल" के नाँव से जानल जाला; २२ नवंबर १८९० के भइल{{Spaced ndash}} ९ नवंबर १९७०) एगो फ्रांसीसी सेना के अधिकारी आ राज्यकर्त्ता रहलें जे [[दुसरा बिस्व जुद्ध]] में नाजी जर्मनी के खिलाफ फ्री फ्रांस के नेतृत्व कइलें आ [[फ्रांस]] में लोकतंत्र के बहाल करे खातिर १९४४ से १९४६ ले फ्रांसीसी गणराज्य के प्रोविजनल सरकार के अध्यक्ष रहलें। १९५८ में, ऊ रिटायरमेंट से बाहर निकललें जब राष्ट्रपति रेने कोटी द्वारा मंत्री परिषद (प्रधानमंत्री) के अध्यक्ष नियुक्त कइल गइल। उ फ्रांस के संविधान के दोबारा लिखले अउरी जनमत संग्रह से मंजूरी मिलला के बाद पांचवा गणराज्य के स्थापना कइले . ओह साल के अंत में ऊ फ्रांस के राष्ट्रपति चुनल गइलें, एह पद पर ऊ १९६५ में दोबारा चुनल गइलें आ १९६९ में इस्तीफा तक ले एह पद पर रहलें। लिले में जनमल, ऊ १९१२ में सेंट-सिर से स्नातक कइलें। [[पहिला बिस्व जुद्ध|पहिला विश्वयुद्ध]] के सजावल अधिकारी रहले, कई बेर घायल भइल रहले अउरी बाद में वर्डन में बंदी बना लिहल गइल रहले . अंतरयुद्ध काल के दौरान, उ मोबाइल बख्तरबंद डिवीजन के वकालत कइले। मई १९४० के जर्मन आक्रमण के दौरान ऊ एगो बख्तरबंद डिवीजन के नेतृत्व कइलें जे आक्रमणकारी लोग पर जवाबी हमला कइलस; एकरा बाद उनुका के युद्ध के उपसचिव नियुक्त कइल गइल। जर्मनी के साथे आपन सरकार के युद्धविराम के स्वीकार करे से इनकार करत डी गॉल इंग्लैंड भाग गइले अउरी 18 जून के आपन अपील में फ्रांसीसी लोग के कब्जा के विरोध करे अउरी लड़ाई जारी रखे के आग्रह कइले| उ मुक्त फ्रांसीसी सेना के नेतृत्व कइले अउरी बाद में धुरी के खिलाफ फ्रांस के राष्ट्रीय मुक्ति समिति के नेतृत्व कइले . अमेरिका से ठंढा संबंध के बावजूद आम तौर प उनुका [[विंस्टन चर्चिल]] के समर्थन रहे अउरी उ फ्री फ्रांस के निर्विवाद नेता के रूप में उभरले . जून १९४४ में उ फ्रांसीसी गणराज्य के अस्थायी सरकार के प्रमुख बनले, फ्रांस के मुक्ति के बाद अंतरिम सरकार रहे| 1944 में ही डी गॉल एगो डिरिजिस्ट आर्थिक नीति शुरू कइलें जेह में पूंजीवादी अर्थब्यवस्था पर राज्य द्वारा निर्देशित पर्याप्त नियंत्रण सामिल रहल जेकरा बाद 30 साल ले अभूतपूर्व बिकास भइल जेकरा के ट्रेंटे ग्लोरियस के नाँव से जानल जाला। नयका चउथा गणराज्य में छोट-मोट पक्षपात के वापसी से निराश हो के ऊ १९४६ के सुरुआत में इस्तीफा दे दिहलें बाकी रासेम्बलमेंट डु पीपुल फ्रांसेज (RPF; "फ्रांसीसी लोग के रैली") के संस्थापक के रूप में राजनीतिक रूप से सक्रिय रहलें। १९५० के दशक के सुरुआत में ऊ रिटायर भइलें आ आपन ''युद्ध संस्मरण'' लिखलें जे जल्दीए आधुनिक फ्रेंच साहित्य के मुख्य हिस्सा बन गइल। जब अल्जीरिया के युद्ध अस्थिर चउथा गणराज्य के फाड़त रहे त मई 1958 के संकट के दौरान राष्ट्रीय सभा उनुका के सत्ता में वापस ले आइल रहे . ऊ एगो मजबूत राष्ट्रपति पद के साथ पांचवा गणतंत्र के स्थापना कइलें, आ ऊ ओह भूमिका में जारी रहे खातिर चुनल गइलें। युद्ध के खतम करे खातिर कदम उठावत घरी ऊ फ्रांस के एक साथ रखे में कामयाब रहलें, जेकरा चलते ''पिड्स-नॉयर्स'' (अल्जीरिया में जनमल जातीय फ्रांसीसी) आ सशस्त्र बल लोग के बहुत नाराजगी भइल; दुनु जने पहिले औपनिवेशिक शासन बनवले राखे खातिर उनुका सत्ता में वापसी के समर्थन कइले रहले. ऊ [[अल्जीरिया]] के आजादी दिहलें आ फ्रांस के अउरी उपनिवेश सभ के प्रति प्रगतिशील रूप से काम कइलें। शीत युद्ध के संदर्भ में डी गॉल आपन "भव्यता के राजनीति" के सुरुआत कइलें, ई दावा कइलें कि फ्रांस के एगो प्रमुख ताकत के रूप में अपना राष्ट्रीय सुरक्षा आ समृद्धि खातिर अमेरिका नियर अउरी देस सभ पर भरोसा ना करे के चाहीं। एकरा खातिर उ "राष्ट्रीय स्वतंत्रता" के नीति के पालन कइले जवना के वजह से उ [[नाटो]] के एकीकृत सैन्य कमान से हट गइले अउरी एगो स्वतंत्र परमाणु हमला बल शुरू कइले जवना से फ्रांस दुनिया के चउथा परमाणु शक्ति बन गइल| ऊ २२ जनवरी १९६३ के एलिसी संधि पर हस्ताक्षर के माध्यम से एंग्लो-अमेरिकी आ सोवियत प्रभाव क्षेत्र सभ के बीच यूरोपीय प्रतिभार पैदा करे खातिर सौहार्दपूर्ण फ्रेंको-जर्मन संबंध सभ के बहाल कइलें। डी गॉल एगो सुपरनेशनल यूरोप के कवनो विकास के विरोध कइले, यूरोप के संप्रभु राष्ट्र के महाद्वीप के रूप में पक्ष में रहले . डी गॉल वियतनाम में अमेरिका के हस्तक्षेप अउरी अमेरिका के डॉलर के " अत्यधिक विशेषाधिकार " के खुल के आलोचना कइले| बाद के सालन में " ''Vive le Québec libre'' " नारा के समर्थन आ यूरोपीय आर्थिक समुदाय में ब्रिटेन के प्रवेश के दू गो वीटो से उत्तरी अमेरिका आ यूरोप दुनों जगह काफी बिबाद पैदा भइल। हालांकि १९६५ में राष्ट्रपति पद खातिर फिर से चुनल गइले, लेकिन मई 1968 में छात्र अउरी मजदूर के ओर से व्यापक विरोध के सामना करे के पड़ल, लेकिन सेना के समर्थन मिलल अउरी राष्ट्रीय सभा में बढ़ल बहुमत के संगे चुनाव जीतले। डी गॉल १९६९ में जनमत संग्रह में हार के इस्तीफा दे दिहलें जवना में ऊ अउरी विकेंद्रीकरण के प्रस्ताव रखलें। एक साल बाद इनके निधन कोलंबई-लेस-ड्यूक्स-एग्लिसेस में इनके आवास पर भइल आ इनके राष्ट्रपति पद के संस्मरण अधूरा रहल। कई गो फ्रांसीसी राजनीतिक दल आ नेता लोग गॉलवादी बिरासत के दावा करे ला; फ्रांस के कई गो गली आ स्मारक उनके निधन के बाद इनके याद में समर्पित कइल गइल। hlkjyn0wjtftn0ugb3tej7n1l4vpe2x शीत युद्ध 0 88989 803353 734477 2026-07-29T16:23:00Z SM7Bot 7808 /* top */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803353 wikitext text/x-wiki {{Infobox}} '''शीत जुद्ध''' एगो शब्द हवे जेकर परयोग आमतौर पर [[अमेरिका]] आ [[सोवियत यूनियन|सोवियत संघ]] आ इनहन के अपना-अपना सहयोगी देस सभ पच्छिमी ब्लॉक आ पूरबी ब्लॉक के बीच भूराजनीतिक तनाव के दौर के कहल जाला। ''शीत युद्ध'' शब्द के प्रयोग एह से कइल गइल बा काहे कि दुनो महाशक्ति के बीच सीधे कवनो बड़ पैमाना पऽ लड़ाई ना भइल रहे, लेकिन ई लोग हर एक प्रमुख क्षेत्रीय संघर्ष में विरोधी पक्ष के समर्थन कइले रहे जवना के प्रॉक्सी युद्ध के नाम से जानल जाला। ई संघर्ष एह दुनों महाशक्ति सभ द्वारा वैश्विक परभाव खातिर वैचारिक आ भूराजनीतिक संघर्ष पर आधारित रहल, १९४५ में नाजी जर्मनी आ शाही जापान के खिलाफ इनहन लोग के अस्थायी गठबंधन आ जीत के बाद परमाणु शस्त्रागार के विकास आ परंपरागत सैन्य तैनाती के अलावा, वर्चस्व के संघर्ष के अप्रत्यक्ष तरीका से व्यक्त कइल गइल जईसे कि मनोवैज्ञानिक युद्ध, प्रचार अभियान, जासूसी, दूरगामी नाकाबंदी, खेल आयोजन में प्रतिद्वंद्विता आ अंतरिक्ष दौड़ जइसन तकनीकी प्रतियोगिता। पच्छिम ब्लॉक के नेतृत्व अमेरिका के साथे साथे कई गो अउरी पहिला दुनिया के रास्ट्र भी करत रहे जवन आम तौर पर उदार लोकतांत्रिक रहे लेकिन अक्सर निरंकुश राज्यन के नेटवर्क से जुड़ल रहे, जवना में से अधिकतर ओह लोग के पूर्व उपनिवेश रहे। {{Sfn|G. Jones|2014|pp=176–179}} {{Efn-ua|[https://www.economist.com/blogs/economist-explains/2013/11/economist-explains-16 "Where did banana republics get their name?" ''The Economist'', 21 November 2013]}} पूरबी ब्लॉक के नेतृत्व सोवियत संघ आ एकरे कम्युनिस्ट पार्टी द्वारा कइल गइल जेकर परभाव पूरा दुसरी दुनिया में रहल आ निरंकुश राज्य सभ के नेटवर्क से भी जुड़ल रहल। अमेरिकी सरकार पूरा दुनिया में कम्युनिस्ट आ दक्षिणपंथी विरोधी सरकार आ विद्रोह के समर्थन कइलस जबकि सोवियत सरकार दुनिया भर में वामपंथी दल आ क्रांति के फंडिंग कइलस जइसे-जइसे लगभग सभ औपनिवेशिक राज्य 1945 से 1960 के दौरान आजादी हासिल कइलें, शीत युद्ध में कई गो तीसरी दुनिया के लड़ाई के मैदान बन गइलें। शीत युद्ध के पहिला चरण १९४५ में [[दुसरा बिस्व जुद्ध]] के खतम होखे के कुछ समय बाद नाँव। अमेरिका अउरी ओकर सहयोगी देस १९४९ में सोवियत हमला के आशंका में [[नाटो|नाटो के]] सैन्य गठबंधन बनवले रहे अउरी सोवियत प्रभाव के ''रोकथाम'' के खिलाफ आपन वैश्विक नीति के नाम देले रहे। सोवियत संघ नाटो के जवाब में १९५५ में वारसॉ पैक्ट के गठन कइले रहे| एह चरण के प्रमुख संकट सभ में १९४८–१९४९ के बर्लिन नाकाबंदी, १९४५–१९४९ के चीनी कम्युनिस्ट क्रांति, १९५०–१९५३ के कोरियाई युद्ध, १९५६ के हंगरी क्रांति, १९५६ के स्वेज संकट, १९६१ के बर्लिन संकट, १९६२ के क्यूबा मिसाइल संकट, आ... १९६४-१९७५ के वियतनाम युद्ध के बारे में बतावल गइल बा अमेरिका अउरी सोवियत संघ के बीच लैटिन अमेरिका, [[मध्य पूरुब|मध्य पूर्व]] अउरी अफ्रीका, एशिया अउरी ओशिनिया के विउपनिवेशीकरण राज्यन में प्रभाव खातिर होड़ लागल रहे। क्यूबा के मिसाइल संकट के बाद एगो नया चरण शुरू भइल जवना में चीन आ सोवियत संघ के बीच चीन-सोवियत के बंटवारा कम्युनिस्ट क्षेत्र के भीतर संबंध के जटिल बना दिहलस, जवना के चलते सीमा पर कई गो मुठभेड़ के सिलसिला सुरू हो गइल, जबकि पच्छिमी ब्लॉक के एगो राज्य फ्रांस अउरी के मांग करे लागल कार्रवाई के स्वायत्तता के बारे में बतावल गइल बा। १९६८ के प्राग वसंत के दबावे खातिर सोवियत संघ चेकोस्लोवाकिया पर हमला कइलस जबकि अमेरिका के नागरिक अधिकार आंदोलन अउरी वियतनाम युद्ध के विरोध से आंतरिक उथल-पुथल के सामना करे के पड़ल। १९६०–१९७० के दशक में दुनिया भर के नागरिकन के बीच एगो अंतर्राष्ट्रीय शांति आंदोलन जड़ जमा लिहलस। परमाणु हथियार के परीक्षण के बिरूद्ध अउरी परमाणु निरस्त्रीकरण खातिर आंदोलन भइल, जवना में युद्ध विरोधी बड़हन विरोध प्रदर्शन भइल | १९७० के दशक ले दुनों पक्ष शांति आ सुरक्षा खातिर भत्ता देवे शुरू क देले रहलें आ एह से डिटेन्ट के दौर के सुरुआत भइल जेह में सामरिक हथियार सीमा वार्ता आ अमेरिका [[चीन|के पीपुल्स रिपब्लिक ऑफ चीन]] के साथ संबंध खोलल सोवियत संघ के सामरिक प्रतिकार के रूप में देखल गइल। १९७० के दसक के दूसरा छमाही में अंगोला, मोजाम्बिक, इथियोपिया, कंबोडिया, अफगानिस्तान आ निकारागुआ समेत कई गो स्वघोषित मार्क्सवादी–लेनिनवादी सरकार के गठन भइल। दशक के अंत में १९७९ में सोवियत-अफगान युद्ध के सुरुआत के साथ डिटेन्ट के पतन हो गइल। 1980 के दशक के शुरुआत एगो अउरी दौर रहे जवना में तनाव बढ़ल रहे| अमेरिका सोवियत संघ पर कूटनीतिक, सैन्य अउरी आर्थिक दबाव बढ़ा दिहलस, ओह समय जब ऊ पहिले से ही आर्थिक ठहराव से ग्रस्त रहे। 1980 के दशक के बीच में, नयका सोवियत नेता [[मिखाइल गोर्बाचोभ|मिखाइल गोरबाचेव]] ''ग्लासनोस्ट'' ("खुलापन", लगभग १९८५) आ ''पेरेस्ट्रोइका'' ("पुनर्गठन", १९८७) के उदारीकरण करे वाला सुधार के सुरुआत कइलें आ १९८९ में अफगानिस्तान में सोवियत सामिल के खतम कइलें। पूरबी यूरोप में राष्ट्रीय संप्रभुता के दबाव अउरी मजबूत हो गइल आ गोरबाचेव अब ओह लोग के सरकारन के सैन्य समर्थन देबे से मना कर दिहलन। १९८९ में पैन-यूरोपीय पिकनिक के बाद लोहा के पर्दा के गिरल आ क्रांति के शांतिपूर्ण लहर ( रोमानिया आ अफगानिस्तान के छोड़ के) पूरबी ब्लॉक के लगभग सगरी सरकार सभ के उखाड़ फेंक दिहलस। सोवियत संघ के कम्युनिस्ट दल खुद एह देस पर नियंत्रण खो दिहलस आ अगस्त १९९१ में गर्भपात करावे वाला तख्तापलट के कोशिश के बाद एकरा पर रोक लगा दिहल गइल। एकरे बाद दिसंबर १९९१ में सोवियत संघ के औपचारिक बिघटन भइल, एकरे घटक गणराज्य सभ के आजादी भइल आ अफिरका आ एशिया के बहुत सारा हिस्सा में साम्यवादी सरकार सभ के पतन भइल। अमेरिका के दुनिया के एकमात्र महाशक्ति के रूप में छोड़ दिहल गइल। शीत जुद्ध आ एकर घटना एगो महत्वपूर्ण विरासत छोड़ले बा। एकरा के अक्सर लोकप्रिय संस्कृति में संदर्भित कइल जाला, खासतौर पर जासूसी आ परमाणु युद्ध के खतरा के बिसय सभ के साथ। बाद के इतिहास खातिर 1989 के बाद से अंतर्राष्ट्रीय संबंध देखल जाय। 326vc5jtvl5fvpjk1aabxw3ljwdg9i2 मनुस्मृति 0 88990 803292 754541 2026-07-29T16:06:15Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803292 wikitext text/x-wiki {{हिंदू धर्म}} '''मनुस्मृति''' [[हिंदू धर्म]] के एगो प्राचीन धर्मशास्त्र (स्मृति) हऽ। ई पहिला संस्कृत ग्रंथ सभ में से एक रहल जे 1776 में अंग्रेजी में अनुवाद कइल गइल, ब्रिटिश भाखा बिज्ञानी सर विलियम जोन्स द्वारा<ref name="Flood 1996, page 56">{{Cite web|url=https://archive.org/details/AnIntroductionToHinduismSeeTantraWesternPhilosophyAscetismFoldersGavinFloodOUP/page/n70|title=Flood (1996), page 56}}</ref> आ एकर इस्तेमाल ब्रिटिश औपनिवेशिक सरकार के लाभ खातिर हिंदू कानून बनावे में कइल गइल। मनुस्मृति में कुल 12 गो अध्याय बा जवना में 2684 श्लोक बा। कुछ संस्करणन में श्लोक के संख्या 2964 बा। एकरा के दुनिया के अइसन पुस्तकन में गिनल जाला, जवना से मनुष्य व्यक्तिगत आचरण आ सामाजिक गठन खातिर प्रेरणा लेले बा। एह में प्रश्न खाली धार्मिक आस्था भा आस्था के नइखे। हर सामाजिक व्यक्ति के ई अपेक्षा आ आकांक्षा होला कि मानव जीवन के जरूरत के कवनो भी तरीका से आपसी सहयोग से आ सुरुचिपूर्ण तरीका से पूरा कइल जा सकेला। बिदेस में एह बिसय पर पर्याप्त शोध भइल बा, तुलनात्मक अध्ययन भइल बा आ आलोचना भी भइल बा। हिन्दू समाज में एकर स्थान [[वेद|वेदत्रायी]] के बाद बा। मनुस्मृति के पचास गो पाण्डुलिपि मिलल बा। समय के साथ कई गो प्रक्षेप भी स्वाभाविक होला। बाद में जवन हिस्सा जोड़ल गइल बा ओकर पहचान कवनो आम आदमी खातिर संभव नइखे। तुलनात्मक अध्ययन के बाद ई काम खाली आधिकारिक विद्वान कर सकेला। === भारत से बाहर प्रभाव=== एंटनी रीड<ref>'''Anthony Reid''' (1988), Southeast Asia in the Age of Commerce, 1450-1680: The lands below the winds, Yale University Press, ISBN 978-0300047509, pages 137-138</ref> के कहनाम बा कि [[म्याँमार|बर्मा]], [[थाइलैंड|थाईलैंड]], [[कंबोडिया]], जावा- बाली आदि में धार्मिक ग्रंथ आ मुख्य रूप से मनुस्मृति के बहुत सम्मान रहे। एह देस सभ में एह ग्रंथ सभ के प्राकृतिक नियम के ग्रंथ मानल जात रहे आ राजा लोग से इनहन के अनुसार काम करे के उमेद कइल जात रहे। एह ग्रंथन के नकल, अनुवाद आ स्थानीय कानून में शामिल कइल गइल।<ref>Victor Lieberman (2014), Burmese Administrative Cycles, Princeton University Press, ISBN 978-0691612812, pages 66-68; Also see discussion of 13th-century Wagaru Dhamma-sattha / 11th century Manu Dhammathat manuscripts discussion</ref><ref>On Laws of Manu in 14th-century Thailand's Ayuthia kingdom named after Ayodhya, see David Wyatt (2003), Thailand: A Short History, Yale University Press, ISBN 978-0300084757, page 61; Robert Lingat (1973), The Classical Law of India, University of California Press, ISBN 978-0520018983, pages 269-272</ref> लुईस जैकोलियट ‘भारत में बाइबिल’ पुस्तक लिखले बाड़न कि: : ''मनुस्मृति ऊ आधारशिला हऽ जवना पर [[मिस्र|मिस्र के]], फारसी, [[यूनान|ग्रेसियन]] आ [[रोम|रोमन]] कानूनी संहिता के निर्माण भइल रहे। आज भी मनु के प्रभाव यूरोप में महसूस कइल जा सकेला।'' : ''(मनुस्मृति ऊ आधार रहल जवना पर मिस्र, फारसी, यूनानी आ रोमन कानून संहिता के निर्माण भइल आ यूरोप में मनु के परभाव आजुओ महसूस कइल जाला।)<ref>V. Krishna Rao. Expansion of Cultural Imperalism Through Globalisation. Manak Publications. p. 82.</ref>'' {{clear}} == संदर्भ == {{Reflist|33em}} [[श्रेणी:हिंदू ग्रंथ]] {{Hinduism-stub}} 6rnp87ee74no0lg9tf1fpf4xslfw62t मध्य प्रदेश के जिला सभ के लिस्ट 0 88991 803308 734496 2026-07-29T16:19:50Z SM7Bot 7808 /* top */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803308 wikitext text/x-wiki [[चित्र:Madhya_Pradesh_district_map.svg|thumb|500x500px|{{Legend|pink|[[Chambal division]]}}{{Legend|green|[[Gwalior division]]}}{{Legend|darkorange|[[Bhopal division]]}}{{Legend|blue|[[Ujjain division]]}}{{Legend|hotpink|[[Indore division]]}}{{Legend|skyblue|[[Narmadapuram division]]}}{{Legend|red|[[Sagar division]]}}{{Legend|turquoise|[[Rewa division]]}}{{Legend|purple|[[Shahdol division]]}}{{Legend|gold|[[Jabalpur division]]}}]] [[भारत के राज्य अउरी संघ राज्यक्षेत्र|भारत]] के [[मध्य प्रदेश]] राज्य 1 नवम्बर 1956 से अस्तित्व में आइल। मध्य प्रदेश में बिबिध भौगोलिक क्षेत्र बाड़ें जिनहन के कौनों आधिकारिक प्रशासनिक सरकारी दर्जा नइखे; कुछ ऐतिहासिक देस, राज्य भा प्रांत सभ से मेल खाला। वर्तमान में राज्य में जिला के संख्या 52 बाटे।<ref>{{Cite web|url=https://mpdistricts.nic.in/|title=DISTRICTS & DIVISIONS OF MADHYA PRADESH|website=District Portal of Madhya Pradesh|publisher=[[मध्य प्रदेश सरकार]]|access-date=9 अप्रैल 2021}}</ref> एह जिला सभ के दस गो प्रशासनिक बिभाजन में बाँटल गइल बा। जिला के तहसील में उपविभाजित कइल गइल बा, जवना में से मध्य प्रदेश में 428 बा। {{clear}} == संदर्भ == {{Reflist|33em}} [[श्रेणी:मध्य प्रदेश]] eo1zzc28qcnu14chjdi0n9870t5uavq सोवियत संघ के बिघटन 0 89253 803348 783771 2026-07-29T16:22:35Z SM7Bot 7808 /* top */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803348 wikitext text/x-wiki {{Infobox event|title=सोवियत संघ के बिघटन|image_name=|Imagesize=300px|image_alt=The Soviet flag being lowered from the Moscow Kremlin and replaced with the flag of Russia|caption=[[सोवियत संघ के झंडा|सोवियत लाल बैनर]] के [[मास्को क्रेमलिन|क्रेमलिन]] से उतारल गइल आ २५ दिसंबर १९९१ के [[रूसी तिरंगा]] से बदल दिहल गइल, [[मिखाइल गोरबाचेव]] के घोषणा के कुछ पल बाद उनकर इस्तीफा, [[बेलोवेझा समझौता]] के मान्यता देत।|participants={{bulleted list|[[Demographics of the Soviet Union#Ethnic groups|Nations]] of the Soviet Union|[[Government of the Soviet Union|Soviet government]]|Governments of the [[republics of the Soviet Union|republics]]|Governments of the [[Autonomous Soviet Socialist Republics|autonomous republics]]|[[Nationalism|Nationalist]] and [[liberalism|liberal]] opposition}}|date={{nowrap|{{start and end dates|१९८८|११|१६|१९९१|१२|२६|df=y}}}}<br />(३ साल,१ महिना अउर १० दिन)|Location={{Collapsible list |title=[[Soviet Union]] |bullets=on |'''[[Post-Soviet states|former]] [[Republics of the Soviet Union|Soviet republics]]''':<br>{{nowrap|{{flagicon|Armenian SSR}}→{{flag|Armenia}}}}<br>{{nowrap|{{flagicon|Azerbaijan SSR}}→{{flag|Azerbaijan}}}}<br>{{nowrap|{{flagicon|Byelorussian SSR}}→{{flag|Belarus|1991}}{{ref label|a|a}}}}<br>{{nowrap|{{flagicon|Estonian SSR}}→{{flag|Estonia}}}}<br>{{nowrap|{{flagicon|Georgian SSR}}→{{flag|Georgia|1990}}}}<br>{{nowrap|{{flagicon|Kazakh SSR}}→{{flag|Kazakhstan}}}}<br>{{nowrap|{{flagicon|Kirghiz SSR}}→{{flag|Kyrgyzstan}}{{ref label|b|b}}}}<br>{{nowrap|{{flagicon|Latvian SSR}}→{{flag|Latvia}}}}<br>{{nowrap|{{flagicon|Lithuanian SSR}}→{{flag|Lithuania|1988}}}}<br>{{nowrap|{{flagicon|Moldavian SSR}}→{{flag|Moldova}}{{ref label|c|c}}}}<br>{{nowrap|{{flagicon|Russian SFSR}}→{{flag|Russia|1991}}}}<br>{{nowrap|{{flagicon|Tajik SSR}}→{{flag|Tajikistan|1991}}}}<br>{{nowrap|{{flagicon|Turkmen SSR}}→{{flag|Turkmenistan|1991}}{{ref label|d|d}}}}<br>{{nowrap|{{flagicon|Ukrainian SSR}}→{{flagicon image|Flag of Ukraine (Soviet shades).svg}} [[Ukraine]]}}<br>{{nowrap|{{flagicon|Uzbek SSR}}→{{flag|Uzbekistan}}}} |'''unrecognized breakaway states''':<br>{{flagicon image|Flag of Artsakh.svg|link=Republic of Artsakh}} [[Republic of Artsakh|Artsakh]] {{small|(1991–present)}}<br>{{flagicon image|Flag of the Chechen Republic (before 2004).svg|link=Chechen Republic}} [[Chechen Republic of Ichkeria#Declaration of independence|Chechnya]] {{small|(1991–[[Second Chechen War|2000]])}}<br>{{flagicon image|Flag of the Gagauz people.svg|link=Gagauz Republic}} [[Gagauz Republic|Gagauzia]] {{small|(1990–1994)}}<br>{{flagicon image|Flag of Azerbaijan.svg|link=Nakhchivan Autonomous Republic}} [[Nakhchivan Autonomous Republic|Nakhchivan]] {{small|(1990–1993)}}<br>{{flagicon|Tatarstan}} [[Tatarstan#Present-day|Tatarstan]] {{small|(1992–1994)}}<br>{{flagicon|Transnistria|state}} [[Transnistria]] {{small|(1990–present)}} }}|result={{bulleted list|[[Dissolution (law)|Dissolution]] of the Soviet Union into [[post-Soviet states|fifteen independent states]]||Establishment of the [[Commonwealth of Independent States]] between eleven independent states|[[Post-Soviet states#Separatist conflicts|Several separatist movements]] in the former [[Autonomous Soviet Socialist Republics|autonomies]] prove successful, most either fail to combat the militaries of their respective republics or agree to rejoin them peacefully|Numerous [[Post-Soviet conflicts|military conflicts]] and ethnic clashes unfold during and after the dissolution, provoking humanitarian crises and leaving many [[internally displaced person|internally displaced]]|Republics declare [[multi party system|multi-party]] [[Presidential republic|presidential]] or [[semi-presidential republic|semi-presidential]] systems|Republics move to adopt [[capitalism|capitalist]] [[market economy]]|[[Soviet ruble|Ruble]] zone active in most of the new states in 1992–94 with national currencies adopted later|Unified [[Soviet Armed Forces]] divided circa 1992–1993|[[Black Sea Fleet]] [[Partition Treaty on the Status and Conditions of the Black Sea Fleet|partitioned]] and negotiation for relocation between Russia and Ukraine in 1997 until 2017; [[Annexation of Crimea by the Russian Federation|terminated in 2014]]|Issues with value loss of savings of former Soviet citizens|Issues with social and medical support for veterans of the [[Soviet–Afghan War]]|Relocation of the [[Soviet Armed Forces]] from [[East Germany]] and the rest of [[Central Europe]]|End of the [[Cold War]]|End of [[Communist Party of the Soviet Union|CPSU]] rule|Decline of [[communism|communist]] movements around the world|The [[China|People's Republic of China]] becomes the last major communist state in the world following [[Chinese economic reforms|economic reforms]]|The [[United States]] becomes the world's sole [[superpower]] }}|partof=[[शीत युद्ध]] आ [[१९८९ के क्रांति]]|notes=<div style="text-align: left">{{nowrap|{{note|a}} Then romanized as ''Byelorussia'' ({{lang-ru|Белоруссия}}).}}<br>{{note|b}} Then romanized as ''Kirghizia'' ({{lang-ru|Киргизия}}).<br>{{note|c}} Then romanized as ''Moldavia'' ({{lang-ru|Молдавия}}).<br>{{note|d}} Then romanized as ''Turkmenia'' ({{lang-ru|Туркмения}}).</div>}}'''सोवियत संघ के बिघटन''' {{Efn|{{lang-rus|Распа́д Сове́тского Сою́за|r=Raspád Sovétskogo Soyúza}}, also negatively connoted as {{lang-rus|Разва́л Сове́тского Сою́за|r=Razvál Sovétskogo Soyúza}}, ''Ruining of the Soviet Union''.}} [[सोवियत यूनियन|सोवियत संघ]] (USSR) के भीतर आंतरिक बिघटन के प्रक्रिया रहल जेकर परिणाम ई भइल कि एह देस आ एकरे संघीय सरकार के अस्तित्व के एगो संप्रभु राज्य के रूप में खतम हो गइल आ एकरे परिणामस्वरूप एकरे घटक गणराज्य सभ के फायदा भइल 26 दिसंबर 1991 के पूरा आजादी मिलल । एकरा से महासचिव (बाद में राष्ट्रपति भी ) [[मिखाइल गोर्बाचोभ|मिखाइल गोरबाचेव]] के सोवियत राजनीतिक अउरी आर्थिक व्यवस्था में सुधार के प्रयास के अंत हो गइल जवना से राजनीतिक गतिरोध अउरी आर्थिक पश्चाताप के दौर के रोके के कोशिश कइल गइल| सोवियत संघ में आंतरिक ठहराव अउरी जातीय अलगाववाद के अनुभव भईल रहे| हालाँकि, अंतिम साल ले ई देस बहुत केंद्रीकृत रहल, पंद्रह गो शीर्ष स्तर के गणराज्य सभ से बनल रहल जे अलग-अलग जातीयता सभ खातिर वतन के रूप में काम करत रहलें। १९९१ के अंत तक एगो विनाशकारी राजनीतिक संकट के बीच कई गो गणराज्य संघ से बाहर निकल चुकल रहे अउरी केंद्रीकृत सत्ता के कम होखे के बीच एकर तीन संस्थापक सदस्यन के नेता लोग घोषणा कइल कि सोवियत संघ के अस्तित्व नइखे रह गइल | एकरा कुछ समय बाद आठ गो अउरी गणराज्य ओह लोग के घोषणा में शामिल हो गइल । दिसंबर १९९१ में गोरबाचेव इस्तीफा दे दिहलें आ सोवियत संसद से जवन कुछ बचल रहे ऊ खुदे खतम करे के वोट दिहलस। ई प्रक्रिया संघ के बिबिध घटक राष्ट्रीय गणराज्य सभ में बढ़त अशांति से शुरू भइल जे इनहन आ केंद्र सरकार के बीच लगातार राजनीतिक आ बिधायी संघर्ष के रूप में बिकसित भइल। एस्टोनिया पहिला सोवियत गणराज्य रहल जे १६ नवंबर १९८८ के संघ के भीतर राज्य संप्रभुता के घोषणा कइलस । लिथुआनिया पहिला गणराज्य रहल जे 11 मार्च 1990 के अधिनियम से सोवियत संघ से पूरा आजादी के घोषणा कइलस आ एकरे बाल्टिक पड़ोसी आ [[पारकॉकेशस|दक्खिनी काकेशस]] गणराज्य [[जॉर्जिया (देश)|जॉर्जिया]] दू महीना के दौरान एकरा में शामिल हो गइलें। अगस्त १९९१ में कम्युनिस्ट कट्टरपंथी अउरी सैन्य अभिजात वर्ग गोरबाचेव के उखाड़ फेंके अउरी असफल सुधार के तख्तापलट में रोके के कोशिश कइलस, लेकिन असफल हो गइल| एह उथल-पुथल के कारण मास्को में सरकार के ज्यादातर परभाव खतम हो गइल आ एकरे बाद के दिन आ महीना सभ में कई गणराज्य सभ आजादी के घोषणा कइलें। बाल्टिक राज्यन के अलगाव के सितंबर १९९१ में मान्यता दिहल गइल। बेलोवेझा समझौता पर ८ दिसंबर के [[रूस]] के राष्ट्रपति बोरिस येल्त्सिन, [[यूक्रेन]] के राष्ट्रपति क्रावचुक, आ [[बेलारूस]] के अध्यक्ष शुशकेविच द्वारा हस्ताक्षर कइल गइल आ एक दुसरा के आजादी के मान्यता दिहल गइल आ सोवियत संघ के जगह स्वतंत्र राज्य सभ के राष्ट्रमंडल (CIS) के निर्माण कइल गइल। [[कजाकिस्तान]] संघ से बाहर निकले वाला आखिरी गणराज्य रहल आ १६ दिसंबर के आजादी के घोषणा कइलस। जॉर्जिया आ बाल्टिक राज्यन के छोड़ के सगरी पूर्व सोवियत गणराज्य २१ दिसंबर के सीआईएस में शामिल भइलें आ अल्मा-आटा प्रोटोकॉल पर हस्ताक्षर कइलें। २५ दिसंबर के गोरबाचेव इस्तीफा दे दिहलें आ आपन राष्ट्रपति के अधिकार – जवना में परमाणु प्रक्षेपण कोड पर नियंत्रण भी शामिल रहे – येल्त्सिन के सौंप दिहलें जे अब [[रूस|रूसी संघ]] के पहिला राष्ट्रपति रहलें। ओह दिन साँझ के क्रेमलिन से सोवियत झंडा उतार के ओकरा जगह पर रूस के तिरंगी झंडा लगावल गइल। अगिला दिने सोवियत संघ के ऊपरी कक्ष के सुप्रीम सोवियत गणतंत्र के सोवियत संघ के औपचारिक रूप से भंग कर दिहलस । <ref> {{Cite news|url=https://www.nytimes.com/1991/12/22/world/end-soviet-union-text-declaration-mutual-recognition-equal-basis.html|title=The End of the Soviet Union; Text of Declaration: 'Mutual Recognition' and 'an Equal Basis'|date=22 December 1991|work=The New York Times|access-date=30 March 2013}} </ref> विघटन के घटना 1989 के क्रांति के प्रमुख निष्कर्ष अउरी शीत युद्ध के अंत भी रहे। <ref>{{Cite news|url=https://www.bloomberg.com/opinion/articles/2021-12-23/30-years-later-soviet-union-s-collapse-still-has-lessons-for-china|title=For China, USSR's 1991 Collapse is Still News It Can Use|date=23 December 2021|work=Bloomberg.com|access-date=4 January 2023|archive-url=https://web.archive.org/web/20230107200259/https://www.bloomberg.com/opinion/articles/2021-12-23/30-years-later-soviet-union-s-collapse-still-has-lessons-for-china|archive-date=7 January 2023}}</ref> शीत युद्ध के बाद, पूर्व सोवियत गणराज्य सभ में से कई गो रूस से गहिराह संबंध बनवले रखले बाड़ें आ बहुपक्षीय संगठन सभ के गठन कइले बाड़ें जइसे कि CIS, सामूहिक सुरक्षा संधि संगठन (CSTO), यूरेशियन आर्थिक संघ (EAEU), आ संघ राज्य, आर्थिक आ सैन्य सहयोग खातिर दिहल गइल बा. दूसर ओर बाल्टिक राज्य आ वारसॉ संधि के पहिले के अधिकतर राज्य [[यूरोपियन यूनियन|यूरोपीय संघ]] (EU) के हिस्सा बन गइलें आ [[नाटो]] में शामिल भइलें जबकि कुछ अउरी पूर्व सोवियत गणराज्य जइसे कि यूक्रेन, जॉर्जिया आ [[मोल्डोवा|मोल्दोवा]] सार्वजनिक रूप से फॉलो करे में रुचि जतवले बाड़ें १९९० के दशक के बाद से इहे रास्ता बा। ba0mq9db1vc6g914vjc6uco1cikls7h ट्राईस्काईडेकाफोबियआ 0 89266 803312 734640 2026-07-29T16:20:06Z SM7Bot 7808 /* top */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803312 wikitext text/x-wiki [[File:P2110024NoThirteenStaAnita_wb.jpg|thumb|सांता अनिता पार्क में स्टॉल के नंबर १२ से १२ए से १४ हो जाला।]] '''त्रिस्काइडकाफोबिया''' ( {{IPAc-en|ˌ|t|r|ɪ|s|k|aɪ|ˌ|d|ɛ|k|ə|ˈ|f|oʊ|b|i|ə}} {{respell|TRIS|kye|DEK|ə|FOH|bee-ə}}, {{IPAc-en|ˌ|t|r|ɪ|s|k|ə|-}} {{respell|TRIS|kə-}} ; {{ety|grc|''τρεισκαίδεκα'' (treiskaídeka)|thirteen|grc|''φόβος'' (phóbos)|fear}} ) <ref>{{Cite web|url=https://www.etymonline.com/word/triskaidekaphobia|title=triskaidekaphobia - Origin and meaning of triskaidekaphobia by Online Etymology Dictionary|website=Etymonline.com|access-date=5 November 2017}}</ref> {{num|13}} संख्या से डर भा परहेज हवे। इहो एगो कारण बा शुक्रवार 13 तारीख के डर के, जेकरा के ''पैरास्केविडेकैट्रिआफोबिया'' ( {{ety|gre|''Παρασκευή'' (Paraskevi)|Friday|gre|''δεκατρείς'' (dekatreís)|thirteen|grc|''φόβος'' (phóbos)|fear}} कहल जाला के नाम से जानल जाला। के बारे में जानकारी दिहल गइल बा। ) या ''फ्रीग्गाट्रिस्काइडकाफोबिया'' ( {{ety|non|Frigg|[[Frigg]]|grc|''τρεισκαίδεκα'' (treiskaídeka)|thirteen|grc|''φόβος'' (phóbos)|fear}} १९९९ के बा। )। एह शब्द के प्रयोग १९१० में ही इसाडोर कोरियाट द्वारा ''असामान्य मनोविज्ञान'' में कइल गइल रहे। <ref>"[https://books.google.com/books?id=Sio_NN91IqYC&printsec=frontcover&dq=Abnormal+psychology,+by+Isador+H.+Coriat.#v=onepage&q=&f=false Abnormal Psychology]" p.&nbsp;319, published in 1910, Moffat, Yard and company (New York). Library of Congress Control No. [http://lccn.loc.gov/10011167 10011167].</ref> c66noige3y9wcr1gecjjf88n5kvr7kw अनिल चौहान 0 89502 803335 786968 2026-07-29T16:21:38Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803335 wikitext text/x-wiki {{Infobox officeholder | image = General Anil Chauhan.jpg | office = भारत के डिफेस्न्स स्टॉफ के प्रमुख | predecessor = [[बिपिन रावत]] | 1blankname = रक्षा मन्त्री | 1namedata = [[राजनाथ सिंह]] | president = [[दौपदी मुर्मू]] | primeminister = [[नरेन्द्र मोदी]] | office1 = 58वां [[चीफ ऑफ स्टाफ कमेटी के अध्यक्ष]] | 1blankname1 = रक्षा मंत्री | 1namedata1 = [[राजनाथ सिंह]] | president1 = [[द्रौपदी मुर्मू]] | primeminister1 = [[नरेंद्र मोदी]] | predecessor1 = [[Manoj Mukund Naravane]] (Acting) | successor1 = | office2 = [[पूर्वी कमान (भारत)|जनरल ऑफिसर कमांडिंग-इन-चीफ पूर्वी कमान]] | 1blankname2 = [[Chief of the Army Staff (India)|Chief of Army Staff]] | 1namedata2 = {{plainlist| * [[बिपिन रावत]] * [[मनोज मुकुन्द नरवणे]] }} | predecessor2 = [[मनोज मुकुन्द नरवणे]] | successor2 = [[मनोज पाण्डे]] | birth_date = {{birth date and age|1961|05|18|df=y}} | death_date = <!-- {{Death date and age|YYYY|MM|DD|YYYY|MM|DD}} death date first, then birth date --> | birth_place = गवाना, [[पौरी गढ़वाल जिला]], [[उत्तर प्रदेश]], [[भारत]]<br /> (अब [[उत्तराखंड]], भारत में) | death_place = | nickname = | birth_name = | spouse = अनुपमा चौहान | children = 1 | allegiance = {{IND}} | branch = {{army|India}} | serviceyears = जून 1981 {{endash}} मई 2021<br>2022 {{endash}} वर्तमान (सीडीएस के रूप में) | rank = [[फ़ाइल: भारत CDS.svg|23px के लिए रैंक प्रतीक चिन्ह]] [[सामान्य (भारत)|सामान्य]] | military_blank1 = [[Service number]] | military_data1 = IC-39492A | unit = [[फाइल: 11वीं गोरखा राइफल्स इन्सिग्निया (इंडिया).svg|20पीएक्स]] [[11वीं गोरखा राइफल्स]] | commands = {{plainlist| *[[फ़ाइल:आईए पूर्वी कमान.jpg|20px]] [[पूर्वी कमांड (भारत)|पूर्वी कमान]] *[[फाइल: स्पीयर कॉर्प्स.jpg|20पीएक्स]] [[III कॉर्प्स (इंडिया)|III कॉर्प्स]] }} | battles = | alma_mater = {{plainlist| * [[राष्ट्रीय रक्षा अकादमी (भारत)|राष्ट्रीय रक्षा अकादमी, खड़गवासला]] * [[भारतीय सैन्य अकादमी]], [[देहरादून]] }} | website = | signature = | signature_alt = }}'''जनरल अनिल चौहान''' (जनम 18 मई 1961) वर्तमान में भारतीय सशस्त्र बल के चीफ ऑफ डिफेंस स्टाफ (CDS) बाड़ें <ref name="Bio">{{Cite web|url=https://www.aninews.in/news/national/general-news/lt-general-anil-chauhan-retd-appointed-as-new-chief-of-defence-staff20220928190853/|title=Lt General Anil Chauhan (retd) appointed as new Chief of Defence Staff|date=28 September 2022|website=www.aninews.in|access-date=7 March 2023|archive-date=29 November 2022|archive-url=https://web.archive.org/web/20221129072154/https://www.aninews.in/news/national/general-news/lt-general-anil-chauhan-retd-appointed-as-new-chief-of-defence-staff20220928190853/|url-status=dead}}</ref><ref>{{Cite web|url=https://www.aajtak.in/india/news/story/government-appoints-retired-lt-general-anil-chauhan-as-chief-of-defence-staff-cds-ntc-1546145-2022-09-28|title=रिटायर्ड लेफ्टिनेंट जनरल अनिल चौहान होंगे देश के नए CDS|date=2022-09-28|website=आज तक|language=hi|access-date=2022-10-04}}</ref> ऊ [[भारतीय सेना]] के फोर स्टार जनरल आ रिटायर्ड ऑफिसर बाड़ें। 30 सितंबर 2022 के इनके रक्षा प्रमुख बनावल गइल <ref>{{Cite web|url=https://www.indiatoday.in/india/story/general-anil-chauhan-takes-charge-as-india-new-chief-of-defence-staff-2006590-2022-09-30|title='Will tackle all challenges': General Anil Chauhan takes charge as Chief of Defence Staff|last=N|last2=DelhiSeptember 30|first2=ini Singh New|website=India Today|language=en|access-date=2022-10-07|last3=September 30|first3=2022UPDATED:|last4=Ist|first4=2022 11:11}}</ref><ref>{{Cite web|url=https://www.aajtak.in/india/news/story/general-anil-chauhan-takes-over-as-cds-pays-tribute-at-national-war-memorial-ntc-1547145-2022-09-30|title=जनरल अनिल चौहान ने संभाला सीडीएस का पदभार, कहा- सभी चुनौतियों का सामना करने को तैयार|date=2022-09-30|website=आज तक|language=hi|access-date=2022-10-07}}</ref> । उ परम विशिष्ठ सेवा पदक, उत्तम युद्ध सेवा पदक, अति विशिष्ठ सेवा पदक, सेवा पदक, विशिष्ठ सेवा पदक के प्राप्तकर्ता हवें। जनरल अनिल चौहान अपना कैरियर में 40 साल से अधिका के कई गो कमान संभाल चुकल बाड़े। उनुका लगे जम्मू-कश्मीर अउरी पूर्वोत्तर भारत में आतंकवाद निरोधक अभियान के अनुभव भी बा। == '''शुरुआती जिनगी आ शिक्षा''' == लेफ्टिनेंट जनरल अनिल चौहान के जनम 18 मई 1961 के उत्तराखंड (तब के उत्तर प्रदेश) के पौरी गढ़वाल जिला के ग्वाना में भइल रहे <ref>{{Cite web|url=https://www.aajtak.in/topic/lt-gen-anil-chauhan|title=Lt Gen Anil Chauhan Latest News, Updates in Hindi {{!}} लेफ्टिनेंट जनरल अनिल चौहान के समाचार और अपडेट - AajTak|website=आज तक|language=hi|access-date=2022-10-04}}</ref> । चौहान आपन शुरुआती स्कूली पढ़ाई कोलकाता के केंद्र विद्यालय फोर्ट विलियम से कइले रहले. बाद में ऊ राष्ट्रीय रक्षा अकादमी, खड़कवासला आ भारतीय सैन्य अकादमी, देहरादून (लेफ्टिनेंट जनरल अनिल चौहान एजुकेशन) से स्नातक कइलें। चौहान के बियाह अनुपमा से भईल बा, जवन कि एगो कलाकार (लेफ्टिनेंट जनरल अनिल चौहान पत्नी) हई। इनकर एगो बेटी बाड़ी प्रज्ञा। == '''सेना में सेवा''' == चौहान के चार दशक के सैन्य कैरियर में 1981 में 11वीं गोरखा राइफल्स में शामिल कइल गइल रहे। चौहान मेजर जनरल के रूप में बारामुल्ला में उत्तरी कमान के एगो पैदल सेना डिवीजन के कमान संभालले रहले, बाद में लेफ्टिनेंट जनरल के रूप में 2017 से 2018 तक [[नागालैंड]] स्थित III कोर के कमांडिंग ऑफिसर के रूप में काम कइले। जनवरी 2018 में इनके महानिदेशक सैन्य अभियान (डीजीएमओ) नियुक्त कइल गइल, एह दौरान ऊ दू गो प्रमुख सैन्य अभियान सभ के निष्पादन के देखरेख कइलें – [[पाकिस्तान]] के खिलाफ 2019 के जवाबी हवाई हमला आ ऑपरेशन सनराइज – भारत-म्यांमार के संयुक्त आतंकवाद विरोधी हमला। सितंबर 2019 में चौहान के पूर्वी कमान के जनरल ऑफिसर कमांडिंग इन चीफ (जीओसी-इन-सी) के रूप में नियुक्त कइल गइल रहे। मई 2021 में रिटायरमेंट ले ऊ एह पद पर रहलें <ref>{{Cite web|url=https://www.aajtak.in/india/news/story/biography-of-new-cds-lieutenant-general-anil-chauhan-balakot-airstrike-pakistan-china-tstrd-1546556-2022-09-29|title=बालाकोट एयरस्ट्राइक के वक्त सेना के DGMO थे अनिल चौहान, चीन मामलों के एक्सपर्ट... अब बने देश के नए CDS|date=2022-09-29|website=आज तक|language=hi|access-date=2022-10-04}}</ref> । == संदर्भ == 2bprt3zej1sdrq7qpdf8b2laugc0nau गढ़वाल राइफल 0 89506 803350 766313 2026-07-29T16:22:46Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803350 wikitext text/x-wiki {{cleanup translation}} {{Infobox military unit|unit_name=गढ़वाल राइफल्स एंड गढ़वाल स्काउट्स|image=Gharwal scouts and Gharwal rifles.jpg|image_size=|caption=गढ़वाल राइफल्स एंड गढ़वाल स्काउट्स का सैन्य चिह्न|dates=1887 – वर्तमान|country={{flag|भारत}}|branch=[[File:Flag of Indian Army.svg|24px]] [[भारतीय थलसेना]]|type=पैदल सेना|size=22 बटालन|command_structure=|garrison=[[लैंसडाउन]], [[गढ़वाल मण्डल|गढ़वाल]], [[उत्तराखण्ड]]|garrison_label=रेजिमेंटल सेंटर|equipment=|equipment_label=|nickname=स्नो लेपर्ड The Royal Garhwalis The Veer Garhwalis Gallant Bhullas|march=बढ़े चलो गढ़वालियों|colonel_of_the_regiment=लेफ्टिनेंट जनरल एन॰एस॰ राजा सुब्रमणि<ref>{{Cite web|url=http://www.newindianexpress.com/cities/thiruvananthapuram/2018/may/07/lt-gen-cherish-mathson-appointed-colonel-of-regiment-of-garhwal-rifles-1811124.html|title=Lt Gen Cherish Mathson appointed Colonel of Regiment of Garhwal Rifles}}</ref>|motto=''युधाया कृत निश्चय'' ''(दृढ़ संकल्प के साथ लड़ो)''|colors=''बद्री विशाल लाल की जय'' ''(भगवान बद्री नाथ के पुत्रों की विजय)''|colors_label=सिंहनाद|anniversaries=5 मई 1887|decorations=3 विक्टोरिया क्रॉस, 1 [[अशोक चक्र]], 4 [[महावीर चक्र]], 14 [[कीर्ति चक्र]], 52 [[वीर चक्र]], 44 [[शौर्य चक्र]]|battle_honours='''स्वतंत्रता के बाद''' टिथवाल, नूरनंग, गदरा रोड, बटुर डुग्रांडी, हिल्ली, बटालिक, ड्रास <!-- Insignia -->|identification_symbol=अशोक प्रतीक के साथ एक माल्टीज़ क्रॉस|identification_symbol_label=बिल्ला|identification_symbol_4_label=रेजिमेंटल ध्वज}} '''गढ़वाल राइफल्स''' भारतीय सेना के एगो सेना रेजिमेंट ह। एकरा के मूल रूप से 1887 में बंगाल सेना के '''39वीं (गढ़वाल) रेजिमेंट''' के रूप में उठावल गइल रहे। एकरा बाद ई ब्रिटिश भारतीय सेना के हिस्सा बन गइल आ भारत के आजादी के बाद ई भारतीय सेना में समा गइल। <ref name=":0">{{Cite web|url=http://www.indianphilately.net/news1116.html|title=Indian Philately Digest : News : November 2016|website=www.indianphilately.net|access-date=2022-04-20}}</ref> ई 19वीं सदी के अंत आ 20वीं सदी के सुरुआत के सीमा अभियान सभ में भी काम कइलस आ साथ ही साथ बिस्व जुद्ध सभ आ आजादी के बाद लड़ल गइल लड़ाई सभ में भी काम कइलस। <ref name=":0" /> ई मुख्य रूप से राजपूत आ ब्राह्मण गढ़वाली के सैनिकन से बनल बा जे मुख्य रूप से गढ़वाल क्षेत्र के सात गो जिला (चमोली, रुद्रप्रयाग, तहरी गढ़वाल, उत्तरकाशी, देहरादून, पौड़ी गढ़वाल, हरिद्वार) से आवेलें। <ref name=":1">{{Cite web|url=https://www.globalsecurity.org/military/world/india/rgt-garhwal.htm|title=Garhwal Rifles|website=www.globalsecurity.org|access-date=2022-04-20}}</ref> आज ई 25,000 से ढेर सैनिकन से बनल बा, जे एकइस गो नियमित बटालियन (अर्थात 2 से 22वीं) में संगठित बाड़ें, गढ़वाल स्काउट जे स्थायी रूप से जोशिमाथ पर तैनात बाड़ें आ 121 इन्फ बीएन टीए आ 127 इन्फ बीएन टीए (इको) आ 14 आरआर दू बटालियन के... 36 आरआर, 48 आरआर बटालियन समेत टेरिटोरियल आर्मी भी ए रेजिमेंट के हिस्सा बा। <ref name=":1" /> तब से 1st बटालियन के मशीनी इन्फैंट्री में बदल दिहल गइल बा आ एकर 6th बटालियन के रूप में मशीनी इन्फैंट्री रेजिमेंट के हिस्सा बा। <ref name=":0" /> रेजिमेंट के निशान में माल्टीज क्रॉस शामिल बा आ ई निष्क्रिय राइफल ब्रिगेड (प्रिंस कंसोर्ट के आपन) पर आधारित बा काहें से कि ई एगो निर्धारित राइफल रेजिमेंट हवे। नियमित राइफल रेजिमेंट के उलट इ भारतीय सेना के समारोह में इस्तेमाल होखेवाला नियमित मार्चिंग रैंक में अयीसन 10 यूनिट में से एगो ह। == आरंभिक इतिहास == 1887 तक गढ़वाल लोग के बंगाल इन्फैंट्री अउरी पंजाब फ्रंटियर फोर्स के गोर्खा के पांच रेजिमेंट में शामिल कइल गइल| 1857 में दिल्ली के घेराबंदी में प्रसिद्धि हासिल करे वाली सिरमुर बटालियन (बाद में 2 गोरखा) के रोल में ओह घरी 33% गढ़वाले रहे। मूल रूप से गढ़वाली लोग के जवन उपलब्धि मिलल रहे <sup>ओकर श्रेय गोरखा लोग के दिहल जात रहे।</sup> गढ़वाली के अलग रेजिमेंट बनावे के पहिला प्रस्ताव जनवरी 1886 में महामहिम लेफ्टिनेंट जनरल, (बाद में फील्ड मार्शल) सर एफएस रॉबर्ट्स, वीसी, भारत के तब के कमांडर इन चीफ द्वारा शुरू कइल गइल। एह हिसाब से अप्रैल 1887 में 3 (द कुमाऊन) गोरखा रेजिमेंट के 2 बटालियन के ऊपर उठावे के आदेश दिहल गइल, जवना में गढ़वाली अउरी कुमाऊनी आदमी के छह गो मिश्रित कंपनी अउरी दू गो गोरखा के चौकोर संरचना रहे| एह फैसला के आधार पर अपर गढ़वाल आ तिहरी राज्य के इलाका में मेजर एल कैम्पबेल आ कैप्टन ब्राउन के ओर से बहाली शुरू कइल गइल| बटालियन के चउथा गोरखा के लेफ्टिनेंट कर्नल ईपी मैनवारिंग उठावले रहले। मेजर एलआरडी कैम्पबेल दूसरा कमान आ लेफ्टिनेंट कर्नल जेएचटी इवाट, एडजुटेंट, दुनो पंजाब फ्रंटियर फोर्स के रहले। लेफ्टिनेंट कर्नल ई.पी. 1891 में दुनो गोर्खा कंपनी आगे चल के 2 बटालियन 3 गोरखा राइफल्स के नाभिक बन गइल अउरी बाकी बटालियन के फिर से बंगाल इन्फैंट्री के 39वीं (गढ़वाली) रेजिमेंट के रूप में नामित कइल गइल| गोरखा लोग के ‘क्रॉस खुकरी’ के जगह ‘फीनिक्स’ ले लिहलस, जवन पौराणिक चिरई शिखा में अपना राख से उठत रहे, जवना से गढ़वाली के औपचारिक शुरुआत एगो अलग वर्ग रेजिमेंट के रूप में भइल। 1892 में 'राइफल्स' के आधिकारिक उपाधि मिल गइल। बाद में ‘फीनिक्स’ के गिरा दिहल गइल, आ माल्टीज क्रॉस जवन राइफल ब्रिगेड (प्रिंस कंसोर्ट के आपन) के इस्तेमाल में रहे, ओकरा के अपनावल गइल। रेजिमेंटल सेंटर के स्थापना 1 अक्टूबर 1921 के लैंसडाउन में भइल। == पहिला विश्वयुद्ध (1914-18) में भइल। == [[चित्र:Stamp_of_India_-_1987_-_Colnect_164954_-_The_Garhwal_Rifles_and_Garhwal_Scouts.jpeg|right|thumb|305x305px| 1987 के डाक टिकट लगावल गइल]] महायुद्ध में फ्रांस के गढ़वाली लोग जे मेरठ डिवीजन के गढ़वाल ब्रिगेड के हिस्सा रहल, फ्लैंडर्स में कार्रवाई में डूब गइल जहाँ दुनों बटालियन बहादुरी से लड़ल। रेजिमेंट के दू गो विक्टोरिया क्रॉस जीते के गौरव रहे; फेस्टुबर्ट में एन के दरवान सिंह नेगी आ न्यूवे चैपल में आरएफएन गबर सिंह नेगी (मृत्यु के बाद)। एनके दरवान सिंह के ई गौरव भी रहल कि ऊ पहिला भारतीय रहलें जिनके राजा सम्राट द्वारा व्यक्तिगत रूप से विक्टोरिया क्रॉस से सम्मानित कइल गइल, सभसे खास रहल फ्रांस में 1 दिसंबर 1914 के लोकोन में भइल लड़ाई के फ्रंट लाइन पर। चूंकि जानमाल के नुकसान बहुत ढेर रहे एह से बटालियन सभ के अस्थायी रूप से मिला के "द गढ़वाल राइफल्स" नाँव दिहल गइल (दुनो गढ़वाल बटालियन सभ के 14 गो अफसर, 15 गो वीसीओ आ 405 लोग फ्रांस में मारल गइल)। फ्रांस में इंडियन कोर के कमान संभाले वाला लेफ्टिनेंट जनरल सर जेम्स विल्कॉक्स अपना किताब "विद द इंडियन इन फ्रांस" में गढ़वाली लोग के बारे में इ बात कहले रहले कि "1 अउरी 2 दुनो बटालियन हर मौका प शानदार प्रदर्शन कइलस, जवना में उ लोग लागल रहे।" ... गढ़वाली लोग अचानक हमनी के सबसे बढ़िया योद्धा के रूप में आगे उछल गइल ... ओ लोग के उत्साह अउरी अनुशासन से बढ़िया कुछूओ ना हो सकत रहे"। [[null|right|link=https://en.wikipedia.org/wiki/File:The_Garhwali_Soldier.jpg|thumb| अदम्य गढ़वाली सैनिक : युद्ध स्मारक, लैंसडाउन]] बाद में 1917 में पुनर्गठित 1 आ 2 बटालियन में मेसोपोटामिया में तुर्कन के खिलाफ कार्रवाई भइल। खान बगदादी में 25–26 मार्च 1918 के लेफ्टिनेंट कर्नल हॉग के कमान में 2 बटालियन खुद के एगो तुर्की कॉलम (जवना में 300 सभ रैंक से बनल रहे, जेह में उनके डिवीजनल कमांडर आ स्टाफ भी शामिल रहल) द्वारा घेरल आ आत्मसमर्पण करे के मजबूर हो गइल। लेफ्टिनेंट कर्नल नम्ब, एमसी के कमान में 1 बटालियन 29-30 अक्टूबर 1918 के शर्कत में तुर्कन के खिलाफ कार्रवाई में आपन अलग पहचान बनवलस। बटालियन के बहादुरी के पुरस्कार मिलल. महान युद्ध, बैटल ऑनर्स "ला बस्सी", "आर्मेंटिएरेस", "फेस्टबर्ट", "न्यूवे चैपल", "ऑबर्स", "फ्रांस एंड फ्लैंडर्स 1914-15", "मिस्र", "में उनके युद्ध कौशल की उचित मान्यता के रूप में. मैसिडोनिया", "खान बगदादी", "शर्कत", "मेसोपोटामिया" आ "अफगानिस्तान" के पुरस्कार एह रेजिमेंट के दिहल गइल. 3 बटालियन के 1916 में आ 4वीं के 1918 में उठावल गइल; एह दुनो बटालियन के अफगानिस्तान अउरी उत्तर-पश्चिमी सीमा में कार्रवाई भईल| (एही बीच 1917 में मौजूदा तीन गो गढ़वाली बटालियन सभ से ड्राफ्ट के माध्यम से 4वीं बटालियन के गठन भइल, जेह में ज्यादातर कुमाऊं आदमी रहलें; एकर नाँव बदल के 4वीं बटालियन 39वीं कुमाऊँ राइफल्स, आ फिर 1918 में 1st बटालियन 50वीं कुमाऊं राइफल्स रखल गइल) . अक्टूबर 1919 में वजीरी आ मशूद के खिलाफ कार्रवाई खातिर 4 बटालियन के कोहाट भेजल गइल| कोहट में ऑपरेशन के सफलतापूर्वक पूरा कइला के बाद बटालियन के कोटकाई के लगे स्पिन घर रिज प बहुत महत्वपूर्ण, लेकिन कठिन घेराबंदी प कब्जा करे के काम दिहल गइल। एकरे परिणामस्वरूप 2 जनवरी 1920 के मशुडो लोग के हमला में कंपनी के कमांडर लेफ्टिनेंट डब्ल्यूडी केनी भारी गोलीबारी अउरी कट्टरपंथी आदिवासी लोग के लहर के नीचे आपन जगह बना लेले| कंपनी के भारी नुकसान भईल बा। आखिरकार जब पिकेट के वापसी के आदेश मिलल त पार्टी प लगातार घात लगा के हमला कइल गइल, जवना के चलते अउरी जानमाल के नुकसान भईल। लेफ्टिनेंट केनी भले ही बहुत घायल हो गइल रहले लेकिन आदिवासी लोग के साहसिक लड़ाई देके अपना आदमी के बाहर निकाले में मदद कइले, जब तक कि अंत में उ गिर गइले अउरी अपना चोट के चलते उनुकर मौत ना हो गइले। भारी संख्या में महसुद के खिलाफ उनुका उभरत बहादुरी खातिर लेफ्टिनेंट डब्ल्यूडी केनी के मरणोपरांत तीसरा विक्टोरिया क्रॉस से सम्मानित कइल गइल। मशहूर स्पिन घर रिज के नाम बदल दिहल गइल आ बाद में एकरा के ‘गढ़वाली रिज’ के नाम से याद कइल गइल। == रॉयल का पुनर्गठन और शीर्षक == [[चित्र:Qissakhwanibazaarmassacre.jpg|right|thumb| किस्सा ख्वानी बाजार नरसंहार में गढ़वाल राइफल्स ड्यूटी से इनकार क देले रहे।]] 1 अक्टूबर 1921 के भारतीय सेना के पुनर्गठन के हिस्सा के रूप में भारतीय सेना में ‘ग्रुप’ प्रणाली शुरू भईल अउरी रेजिमेंट 18वां भारतीय पैदल सेना समूह बन गइल| एही दिन लेफ्टिनेंट कर्नल केनेथ हेंडरसन, डीएसओ के नेतृत्व में चउथा बटालियन के समूह के प्रशिक्षण बटालियन के रूप में नामित कइल गइल| एही साल 1 दिसंबर के एकर नाम बदल के 10/18वीं रॉयल गढ़वाल राइफल्स रखल गइल। आज भी दिग्गज लोग बोलचाल में एह रेजिमेंट के गढ़वाल ग्रुप के नाम से संबोधित करेला। 2 फरवरी 1921 के दिल्ली में अखिल भारतीय युद्ध स्मारक (अब ‘इंडिया गेट’ कहल जाला) के शिलान्यास के ऐतिहासिक अवसर पर कनाट के ड्यूक घोषणा कइले कि विशिष्ट सेवा आ बहादुरी के सम्मान में सम्राट छह गो पुरस्कार दिहले बाड़न यूनिट आ दू गो रेजिमेंट के ‘रॉयल’ के उपाधि से सम्मानित कइल गइल जवना में से एगो रेजिमेंट रहे. ‘रॉयल’ गढ़वाल राइफल्स के दाहिना कंधा प लाल रंग के मुड़ल लाल रस्सी (रॉयल रोप) अउरी कंधा के टाइटिल प ट्यूडर मुकुट पहिनला के विशेष निशान दिहल गइल। ‘शाही’ के उपाधि आ ताज 26 जनवरी 1950 के गिरावल गइल, जब भारत गणतंत्र बनल, हालांकि डोरी आपन भेद बरकरार रखले बा। == द्वितीय विश्वयुद्ध (1939-45) में भइल। == दुसरा बिस्व जुद्ध के सुरुआत के बाद एह रेजिमेंट के बिस्तार भइल आ 1940 में 4वीं बटालियन के फिर से उठावल गइल; 5वीं के स्थापना 1941 में भइल रहे। 11वीं (टेरिटोरियल) बटालियन के 1939 में पेशावर में संचार सुरक्षा के काम खातिर पलल बढ़ल; 1941 में एकरा से 6वीं बटालियन के गठन भईल| दुसरा बिस्व जुद्ध में गढ़वाली लोग के सक्रिय भागीदारी भइल, बर्मा में 1 आ 4 बटालियन, मलय में 2 आ 5 बटालियन के कार्रवाई देखल गइल। दूसरा बटालियन 1940 में मलय प्रायद्वीप के कुआंतानी में गैरीसन बटालियन रहे| कुआंटन में सेना के एकमात्र बटालियन, एकरा के एगो व्यापक रूप से बिखराइल इलाका पर असंख्य काम खातिर मैदान में उतारल गइल रहे। जापानी आक्रमण से ठीक पहिले एकरा के दू बेर दुहावल गइल ताकि नया बटालियन बनावे में मदद मिल सके। जब जापानी हमला कइले त बटालियन बहादुरी से लड़लस, जवना में भारी नुकसान भईल। बटालियन के बैटल ऑनर ‘कुआंटन’ आ थिएटर ऑनर ‘मलया 1941-42’ से सम्मानित कइल गइल. भारी जानमाल के नुकसान के कारण मलय अभियान के बाद 2 बटालियन के अस्तित्व खतम हो गइल – अवशेष जापानी लोग द्वारा पकड़ लिहल गइल। नवगठित 5वीं बटालियन के दिसंबर 1941 में विदेश में आदेश दिहल गइल रहे, जबकि अभी भी कच्चा अउरी कम सुसज्जित रहे| ई मध्य पूर्व खातिर रवाना हो गइल, हालाँकि सिंगापुर गइला के बाद गंतव्य बदल दिहल गइल। बटालियन मूर, जोहोर में कुछ उल्लेखनीय कार्रवाई आ फिर सिंगापुर जाए के रास्ता में लंबा, कड़वा रियरगार्ड कार्रवाई लड़लस। 7वीं बटालियन के अनिवार्य रूप से एह दुनो बटालियन के जगह के रूप में उठावल गइल (बाद में एकरा के प्रशिक्षण भूमिका में बदल दिहल गइल)। दूसरा बटालियन के 1946 में युद्ध के बाद ही फिर से उठावल गइल| 5वीं के फेर से खड़ा करे खातिर 1962 तक इंतजार करे के पड़ी| 1941 में 1 बटालियन बर्मा गइल अउरी जापानी ज्वार के रोके के कोशिश में बहादुरी से लड़ाई कइलस| ई दक्षिणी शान राज्यन के येनंगयुंग में भइल हताश लड़ाई में भाग लिहलस, जेकरा खातिर एकरा के लड़ाई के सम्मान के रूप में सम्मानित कइल गइल। एकरे अलावा एकरा के बैटल ऑनर "मोनवा" के एकलौता पुरस्कृत होखे के गौरव बा, ई रिट्रीट फ्रॉम बर्मा में आखिरी बड़हन एक्शन रहल। कुछ समय के आराम अउरी जंगल के गहन प्रशिक्षण के बाद फिर से समूहबद्ध होखे के बाद बटालियन के फिर से समूह बनावे खातिर बर्मा वापस कर दिहल गइल| अरकान में एकर कार्रवाई, न्गाक्यादौक दर्रा, रामरी में उतरल आ रंगून में अंतिम प्रवेश से एकरा के अउरी लड़ाई के सम्मान मिलल: "उत्तर अरकान", "ंगाक्यडुक दर्रा", "रामरी" आ "तुंगुप", आ थिएटर ऑनर "बर्मा 1942-". 45" के बा। NW फ्रंटियर पर लगभग तीन साल के बाद 4 बटालियन के भी बर्मा के आदेश दिहल गइल| जंगली युद्ध के गहन प्रशिक्षण के बाद ई बर्मा चल गइल आ सुरंग के मैदान अक्याब आ फिर कुआलालंपुर में जाए से पहिले कुआलालंपुर में आत्मसमर्पण करे वाला जापानी लोग के निहत्था करे खातिर कई गो कार्रवाई लड़लस। उत्तरी अफ्रीका आ इटली के नाम से जानल जाला। 3 बटालियन एबिसिनिया, पश्चिमी रेगिस्तान, मिस्र, साइप्रस, इराक, सीरिया, फिलिस्तीन आ अंत में इटली में अभियान में काम कइलस। एबिसिनिया में दुसरा बिस्व जुद्ध के सुरुआती दौर में ई इटैलियन लोग के खिलाफ आपन छाप छोड़लस आ पूरा अभियान में खुद के अलग कइलस। एकरा युद्ध सम्मान में तीन गो 'गढ़वाली-केवल' सम्मान बा: "गल्लाबत", "बरेंटू" आ "मसावा"। एकरे बाद अउरी लड़ाई के सम्मान मिलल: "केरेन", "अम्बा अल्गी", "सिटा डी कैस्टेलो", आ थिएटर के सम्मान "उत्तर अफिरका 1940-43" आ "इटली 1943-45" के, बिबिध लड़ाई के मैदान सभ पर मजबूत शौर्य के गवाही आ... थियेटरन में बा।दे रहल बा। युद्ध के अंत अउरी एकरे परिणामस्वरूप डिमोबिलाइजेशन के बाद रेजिमेंट के लगे तीन गो नियमित बटालियन 1, 2 अउरी 3 हो गइल| एह तरह से आजादी के समय गढ़वाल राइफल्स में मात्र तीन गो सक्रिय बटालियन रहे| == आजादी के बाद == 1947 में भारत के गठन आ ओह समय भारत में बिबिध राज्य सभ के विलय के बाद रियासत गढ़वाल पहिला रियासत रहल जे भारतीय संघ में विलय भइल। एकरा बाद रेजिमेंट के तबादला नव स्वतंत्र भारतीय सेना में कर दिहल गइल| 3 बटालियन जम्मू कश्मीर ऑपरेशन में विशिष्टता के साथ भाग लिहलस, बैटल ऑनर "तिथवाल" जीतलस आ आजादी के बाद से कवनो एक ऑपरेशन में भारतीय सेना के सबसे सजावल बटालियन में से एगो बने के गौरवशाली मिलल - एकरा के एमवीसी, 18 वोन जीतल वीआरसी, 01 एससी के बा। (तब अशोक चक्र वर्ग III कहल जाला) आ 19 गो मेन्शन-इन-डिस्पैच। कमांडिंग ऑफिसर लेफ्टिनेंट कर्नल कमन सिंह के एगो हकदार महावीर चक्र से सम्मानित कइल गइल आ उनकर नाम ‘कमन सेतु’ के रूप में जिंदा बा, जवन कि पोक आ जम्मू-कश्मीर के बीच हाल ही में खुलल क्रॉसिंग प्वाइंट (अब ‘अमन कामन सेतु’ के नाम बदलल गइल बा) के रूप में जिंदा बा। 1950 में जब भारत गणतंत्र बनल त रेजिमेंट के नाम से शाही उपाधि हटा दिहल गइल। अंग्रेजन से जुड़ल अउरी रेजिमेंटल निशान भी बंद कर दिहल गइल, हालांकि रेजिमेंट के डोरी के दाहिना कंधा प पारंपरिक ‘शाही’ फैशन में पहिरल जारी रहल| 1953 में रेजिमेंट के 3 बटालियन कोरिया में संयुक्त राष्ट्र के गार्जियन फोर्स में योगदान दिहलस| == 1962 के चीन-भारत के युद्ध == 1962 के चीन-भारत संघर्ष में नेफा के तवांग, जंग आ नुरानांग सेक्टर में भारी लड़ाई के बीच 4 बटालियन (ओह समय के रेजिमेंट के सबसे कम उमिर के बटालियन) के देखल गइल, जहाँ ऊ अपना बारे में बेहतरीन ब्यौरा दिहलस, बहुत नुकसान उठावलस भारी जानमाल के नुकसान भइल. , 1999 में भइल रहे। नुरानांग में बटालियन के स्टैंड के एह लड़ाई के अधिकतर वर्णन में "सेना के लड़ाई के सभसे बढ़िया उदाहरण" के रूप में एकल कइल गइल बा। भारी विषमता के खिलाफ बहादुरी से खड़ा होखे खातिर 4 गढ़ आरआईएफ के बैटल ऑनर "नुरनंग" से सम्मानित कइल गइल – एकलौता बटालियन जेकरा के नेफा में बैटल ऑनर से सम्मानित कइल गइल, जवन कि ओह खास संघर्ष के संदर्भ में एगो एकल भेद रहे। नूरानांग के नाम बदल के जसवंतगढ़ रखल गइल आरएफएन जसवंत सिंह रावती के सम्मान में, जिनकर नूरानांग में बहादुरी के मरणोपरांत महा वीर चक्र से सम्मानित कइल गइल। एह संघर्ष में जीतल दूसरा महा वीर चक्र लेफ्टिनेंट कर्नल (बाद में मेजर जनरल) बीएम भट्टाचार्य के जीतल रहे, जे अथक कमांडिंग ऑफिसर रहले, जेकरा नेतृत्व में चउथा बटालियन चीनी के खूनी नाक दे दिहलस। कैद में बटालियन के बचे वाला लोग के चीनी POW कैंप में अतिरिक्त सजा खातिर चुनल गइल काहे कि चीनी लोग के गढ़वाली लोग के हाथ से भारी नुकसान भइल रहे। नुरानांग में बटालियन के पौराणिक कार्रवाई लोककथा में आ गइल बा. बटालियन के मिलल बहादुरी के पुरस्कार दू गो महा वीर चक्र, सात गो वीर चक्र, एगो सेना मेडल आ एगो मेन्शन इन डेस्पेच रहल. == 1965 के भारत-पाकिस्तान युद्ध == 1965 में राजस्थान के बार्मर सेक्टर में पहिला बटालियन शानदार कार्रवाई कइलस, जवना खातिर बाद में एकरा के बैटल ऑनर "गदरा रोड", ओपी हिल में 2 बटालियन, फिलोरा में 6 बटालियन अउरी बुतुर डोग्रांडी में 8 बटालियन से सम्मानित कइल गइल। दू दिन का भीतर ओकरा दू गो वरिष्ठ अधिकारी लेफ्टिनेंट कर्नल जेई झीराद आ मेजर एआर खान के नुकसान हो गइल. एकरा बाद कमान सबसे छोट कैप्टन (लेफ्टिनेंट कर्नल) एचएस रौटेला के हाथ में चल गइल जवन मोर्चा से नेतृत्व कइले अउरी बहादुरी खातिर तब के चीफ ऑफ आर्मी स्टाफ द्वारा तारीफ कइल गइल| रेजिमेंट के कैप्टन सीएन सिंह के मरणोपरांत 120 इन्फैंट्री ब्रिगेड मुख्यालय में सेवा में रहत बहादुरी खातिर एमवीसी से सम्मानित कइल गइल। पहिला बटालियन आ आठवीं बटालियन के क्रमशः बैटल ऑनर्स ‘गदरा रोड’ आ ‘बुट्टूर डोगरांडी’ से सम्मानित कइल गइल. छठवीं बटालियन आ 8वीं बटालियन के रंगमंच सम्मान ‘पंजाब 1965’ से भी सम्मानित कइल गइल। गदरा शहर के लड़ाई के दौरान पहिला बटालियन राजस्थान सेक्टर में रहे आ गदरा शहर पर कब्जा करे के अभियान में आपन अलगा पहचान बनवलस, बिना तोपखाना के समर्थन के रेगिस्तानी इलाका में सेना के रणनीति के बढ़िया प्रदर्शन कइलस। बटालियन जेसी के पार, नवा ताला आ मियाजलर पर कब्जा कर लिहलसि. वीर चक्र से सम्मानित लोग में सीओ लेफ्टिनेंट कर्नल केपी लाहिरी भी शामिल रहे। बटालियन के बैटल ऑनर ‘गदरा रोड’ आ थिएटर ऑनर ‘राजस्थान 1965’ मिलल. कप्तान नरसिंह बहादुर सिंह जी के अहम भूमिका रहल आ उनुका बहादुरी आ साहसी प्रयास खातिर उनुका के बटालियन के मिलल ‘सेना मेडल’ बहादुरी के पुरस्कार, थारी वीर चक्र आ पांच गो मेन्शन इन डेस्पैच से सम्मानित कइल गइल. ऑपरेशन हिल के दौरान 2 बटालियन 'ओपी हिल' प दुगो हमला में भाग लेलस| 2 बटालियन के कैप्टन चंद्र नारायण सिंह मुख्यालय 120 इन्फैंट्री ब्रिगेड से जुड़े रहे। गलुठी इलाका में हमलावरन का खिलाफ रात में भइल बहादुरी से भइल कार्रवाई में ऊ ओह चार्ज के नेतृत्व कइलन जवना में दुश्मनन के छह गो मारल गइल जबकि बाकी लोग भारी मात्रा में हथियार, गोला बारूद आ उपकरण छोड़ के भाग गइल. एह कार्रवाई में कैप्टन सीएन सिंह के मशीनगन फटला से मारल गइल आ उनुकर जान चल गइल. उनुका के मरणोपरांत महा वीर चक्र से सम्मानित कइल गइल. 3 बटालियन लाहौर सेक्टर में रहे, आ जीटी रोड पर अग्रिम में भाग लिहलस। एकरा में 33 लोग के जानमाल के नुकसान भइल जवना के अधिकतर कारण दुश्मन के तोपखाना के बहुते भारी गोलाबारी भइल. 6वीं बटालियन सियालकोट में रहे जहाँ युद्ध के कुछ सबसे भयंकर लड़ाई भईल| शुरुआती चरण में बटालियन चरवा पर कब्जा कर लिहलस। एकरे बाद ई फिलाउरा के दृढ़ता से पकड़ लिहलस, दुश्मन के कई गो हमला के पीछे छोड़ दिहलस। 8वीं बटालियन भी सियालकोट सेक्टर में रहे, आ बतुर डोग्रांडी खातिर कड़ा लड़ाई लड़लस, जवना में भारी लड़ाई के दू दिन के भीतर कमांडिंग ऑफिसर आ 2आईसी के नुकसान समेत भारी कीमत चुकावे के पड़ल। एह कार्रवाई सभ से बैटल ऑनर "बतुर डोगरंडी" आ थिएटर ऑनर "पंजाब 1965" के माध्यम से रेजिमेंट के अउरी महिमा आइल। बटालियन के मिलल बहादुरी के पुरस्कार वीर चक्र रहे। [[चित्र:A_Garhwal_Rifles_recruit_in_the_1970s.jpg|right|thumb| 1970 के दशक में गढ़वाल राइफल्स भर्ती।]] == 1971 के युद्ध में भइल == बांग्लादेश के मुक्ति के अभियान के दौरान 5वीं बटालियन एगो यशस्वी रास्ता देखवलस| युद्ध में अपना हरकत खातिर बटालियन के बैटल ऑनर 'हिली' आ थिएटर ऑनर 'ईस्ट पाकिस्तान 1971' से सम्मानित कइल गइल। बटालियन के तीन गो वीर चक्र, तीन गो सेना मेडल आ सात गो मेन्शन इन डेस्पैच मिलल. 12वीं बटालियन अक्टूबर 1971 से एक्शन में रहे अउरी सक्रिय दुश्मनी शुरू होखला प दिनाजपुर के पूरब में सीमांकन अउरी ऑपरेशन में भाग लेलस| 3 बटालियन शकरगढ़ सेक्टर में रहे। ई आपन सुरुआती उद्देश्य धनदार आ मुखवाल (सुचेतगढ़ के दक्खिन) ले गइल आ फिर दुश्मन के इलाका में बारी आ लैसर कलां ले गइल। युद्धविराम के समय तक बटालियन चक्र के उत्तर में रामरी तक आगे बढ़ गइल रहे| बटालियन के मिलल बहादुरी के पुरस्कार वीर चक्र आ सेना मेडल रहल. चौथा बटालियन झंगार सेक्टर में रहे अउरी आपन जमीन पकड़ के दुश्मन के ठिकाना प छापा मारत रहे| 6वीं बटालियन सियालकोट सेक्टर में रहे। नवनपिंड पर फिर से कब्जा कइला के बाद बटालियन दुश्मन के इलाका के गहिराई में रक्षात्मक लड़ाई लड़लस, जवना से अपना सेक्टर के सामने दुश्मन के ठिकाना प तीन गो मजबूत छापामारी के भगा दिहलस। बटालियन के मिलल बहादुरी के पुरस्कार एगो वीर चक्र आ दू गो मेन्शन इन डेस्पैच रहे. 7वीं बटालियन छंब सेक्टर, 1999 में रहे। मेजर एचएस रौतेला एसएम (अब लेफ्टिनेंट कर्नल) के कमान में 8वीं बटालियन भी पंजाब में होल्डिंग रोल में रहे अउरी दुश्मन के चौकी घुरकी प कब्जा क लेलस अउरी सेना मेडल से सम्मानित कइल गइल| युद्धविराम तक जारी गोलाबारी के बावजूद उ ए चौकी प रहले। बटालियन के मिलल बहादुरी के पुरस्कार दू गो सेना मेडल रहल. मेजर महाबीर नेगी के नेतृत्व में दसवीं बटालियन अखनूर-जौरियान सेक्टर में रायपुर क्रासिंग प कब्जा क के एगो उल्लेखनीय कार्रवाई कइलस। एक हमला में कमांडिंग ऑफिसर लेफ्टिनेंट कर्नल ओंकर सिंह निजी तौर प नेतृत्व कइले, गंभीर रूप से घाही हो गइले अउरी बाद में उनुकर मौत हो गइल। == शांति सेना के ओर से ऑपरेशन पवन (1987-88)। == एह अभियान में गढ़वाल राइफल के कुछ बटालियन के 1987 में वर्तमान प्रधानमंत्री श्री राजीव गांधी जी भेजले रहले| भारत के शांति सेना द्वारा ऑपरेशन पवन के दौरान 5वीं बटालियन अउरी 11वीं बटालियन लिट्टे (लिबरेशन टाइगर्स ऑफ तमिल ईलम) के कमान के भगावे के काम प रहे| श्रीलंका के स्थानीय क्षेत्र में बा। एह काम में लिट्टे के हमला में घायल बटालियन के एगो सदस्य के बचावे खातिर 5वीं बटालियन के राइफलमैन कुलदीप सिंह भंडारी के 11वीं बटालियन में शिफ्ट कर दिहल गइल। 11वीं बटालियन के बाकी सदस्यन के जान बचा के अदम्य हिम्मत देखवले आ वीर चक्र से सम्मानित भइले. [[चित्र:18_Garhwal_Riles_Point_4700.jpg|thumb|बिंदु 4700 पर बा। पर हमला से पहिले 18वीं बटालियन के जवान...]] == कारगिल युद्ध == 17वीं बटालियन बटालिक उपक्षेत्र में रहे आ ओकरा के राष्ट्रीय राजमार्ग के किनारे जुबर टॉप के नजारा देखे वाला रिजलाइन जुबर हाइट्स के एरिया बम्प आ कालापथर पर हमला करे के काम दिहल गइल रहे। चढ़ाई कठिन रहे आ कैप्टन जिंटू गोगोई के पलटन के छोड़ के सगरी कंपनी ‘दिन के उजाला’ में रहे। बहादुर ‘भुल्लोन’ दुश्मन के भारी गोलीबारी का सोझा कालापथर पर कब्जा कर लिहले, आ फेर दुश्मन के यूएमजी स्थापन से आमने सामने आ गइले. दुश्मन के पूरा हैरानी के बात रहे कि कैप्टन गोगोई तुरंत यूएमजी सेंगर प हमला कइले, जवना में हाथ से हाथ मिला के भईल लड़ाई में दुगो घुसपैठिया के मौत हो गइल, जवना में उ जानलेवा रूप से घायल हो गइले। अपना सुरक्षा के अनदेखी करे में बहादुरी खातिर कैप्टन जिंटू गोगोई के मरणोपरांत वीर चक्र से सम्मानित कइल गइल बाकिर दुख के बात बा कि कैप्टन विक्रम बत्रा का तरह जे कारगिल शहीद भी रहले, उनुकर नाम के कवनो खास जानकारी नइखे Is known. बटालियन अगिला दिनन में ताजा हमला कइलस आ बम्प आ कालापथार पर कब्जा कर लिहलस। एह से अउरी सफलता के रास्ता खुलल – बटालियन आगे बढ़ के मुंथो ढलो परिसर में एगो अउरी प्रमुख फीचर ले लिहलस, अंत में भारी बर्फबारी आ फीचर के LoC के नजदीकी के कारण तोपखाना समेत दुश्मन के कारगर फायरिंग के बावजूद बिंदु हासिल कइलस।5285 ले लिहलस। ऑपरेशन विजय में भइल कारनामा खातिर एह बटालियन के बैटल ऑनर ‘बटालिक’ से सम्मानित कइल गइल. बटालियन के मिलल बहादुरी के पुरस्कार वीर चक्र आ मेन्शन इन डेस्पैच रहे। एकरा अलावा एह यूनिट के बेहतरीन काम खातिर जीओसी-इन-सी नॉर्दर्न कमांड यूनिट एप्रिसिएशन से भी सम्मानित कइल गइल। अंत में भारी बर्फबारी आ सुविधा के एलओसी से नजदीक होखे के कारण तोपखाना समेत दुश्मन के कारगर गोलीबारी के बावजूद प्वाइंट 5285 पर कब्जा कर लिहल गइल। ऑपरेशन विजय में भइल कारनामा खातिर एह बटालियन के बैटल ऑनर ‘बटालिक’ से सम्मानित कइल गइल. बटालियन के मिलल बहादुरी के पुरस्कार वीर चक्र आ मेन्शन इन डेस्पैच रहे। एकरा अलावा एह यूनिट के बेहतरीन काम खातिर जीओसी-इन-सी नॉर्दर्न कमांड यूनिट एप्रिसिएशन से भी सम्मानित कइल गइल। अंत में भारी बर्फबारी आ सुविधा के एलओसी से नजदीक होखे के कारण तोपखाना समेत दुश्मन के कारगर गोलीबारी के बावजूद प्वाइंट 5285 पर कब्जा कर लिहल गइल। ऑपरेशन विजय में भइल कारनामा खातिर एह बटालियन के बैटल ऑनर ‘बटालिक’ से सम्मानित कइल गइल. बटालियन के मिलल बहादुरी के पुरस्कार वीर चक्र आ मेन्शन इन डेस्पैच रहे। एकरा अलावा एह यूनिट के बेहतरीन काम खातिर जीओसी-इन-सी नॉर्दर्न कमांड यूनिट एप्रिसिएशन से भी सम्मानित कइल गइल। 18वीं बटालियन के प्वाइंट 4700 अउरी ओकरा आसपास के ऊंचाई प कब्जा करे के काम दिहल गइल रहे जहाँ दुश्मन टोलोलिंग अउरी 5140 से बाहर निकालला के बाद आपन ठिकाना मजबूत कइले रहे| एकरा बाद के अभियान में भुल्ला उच्चतम स्तर के बहादुरी के प्रदर्शन कइले। कैप्टन सुमीत राय एगो साहसिक हमला के नेतृत्व कइलन, एगो सरासर ढलान पर चढ़ गइलन आ दुश्मन सेंगर के हैरान कर दिहलन. एह लोग के सफलता से पीटी 4700 पर कब्जा करे के राह खुल गइल. बाद में दुश्मन के गोलीबारी से ई बहादुर अधिकारी गंभीर रूप से घायल हो गइल आ अपना चोट का चलते ओकर मौत हो गइल. कप्तान सुमीत राय के मरणोपरांत वीर चक्र से सम्मानित कइल गइल. पास के फीचर प 18वीं के अवरू कंपनी हमला कइले। चार्ज के अगुवाई करत मेजर राजेश साह अउरी कैप्टन एमवी सूरज कई गो गढ़ से दुश्मन के भगा देले। एह दुनु वीर अफसरन के साहसी नेतृत्व आ बहादुरी खातिर वीर चक्र से सम्मानित कइल गइल. 18वीं बटालियन के भुल्लन अदम्य हिम्मत के प्रदर्शन कइलन, एनके कश्मीर सिंह, आरएफएन अनुसुया प्रसाद आ आरएफएन कुलदीप सिंह सभे मरणोपरांत वीर चक्र जीतले. बटालियन के सीओएएस यूनिट प्रशस्ति पत्र के तत्काल पुरस्कार मिलल जवना में छह गो वीर चक्र शामिल बा, एक बेर सेना मेडल, सात गो सेना मेडल, सात गो मेन्शन इन डेस्पैच आ बैटल ऑनर ‘ड्रास’ के पुरस्कार मिलल. दुनु बटालियन के थिएटर ऑनर ‘कारगिल’ से सम्मानित कइल गइल. एह सम्मानन के बहुते कीमत चुकावे के पड़ल जवना में रेजिमेंट के हर रैंक के 49 गो जवानन के नुकसान भइल जवना के कार्रवाई में मारल गइल. कारगिल में 17वीं आ 18वीं बटालियन के अलावा रेजिमेंट के 10वीं बटालियन भी तैनात रहे। एह बटालियन के अनुकरणीय प्रदर्शन खातिर जीओसी-इन-सी नॉर्दर्न कमांड यूनिट एप्रिसिएशन आ चार गो सेना मेडल से सम्मानित कइल गइल. [[चित्र:The_Garhwal_Rifles_marching_contingents_passes_through_the_Rajpath,_on_the_occasion_of_the_67th_Republic_Day_Parade_2016,_in_New_Delhi_on_January_26,_2016.jpg|alt=|right|thumb|305x305px| 67वीं गणतंत्र दिवस परेड, 2016 के अवसर पर राजपथ से गुजरते गढ़वाल राइफल्स टुकड़ी]] == इकाइयों == निम्नलिखित इकाई सभ गढ़वाल राइफल्स के हिस्सा हवें:<ref name=":1"/> * 1st बटालियन (6 मेक इन्फ में बदलल गइल) * 2 बटालियन (विक्टोरिया क्रॉस प्लाटून, न्यूवे चैपल, शानदार सेकंड) के बा। * तीसरी बटालियन (दांतवाल या तीसरी) के बा। * 4वीं बटालियन (नुरनांग बटालियन) के बा। * 5वीं बटालियन (हिल) के बा। * 6वीं बटालियन के बा * 7वीं बटालियन के बा * 8वीं बटालियन के बा * 9वीं बटालियन के बा * दसवीं बटालियन के बा * 11वीं बटालियन के बा * 12वीं बटालियन के बा * 13वीं बटालियन के बा * 14वीं बटालियन के बा * 15वीं बटालियन के बा * 16वीं बटालियन के बा * 17वीं बटालियन (बटालिक कारगिल) के बा। * 18वीं बटालियन (ड्रास बटालियन) के बा। * 19वीं बटालियन के बा * 20वीं बटालियन के बा * 21वीं बटालियन के बा * 22वीं बटालियन के बा * 121 पैदल सेना बटालियन क्षेत्रीय सेना (गढ़वाल) भारतीय सेना रिजर्व : कोलकाता, पश्चिम बंगाल * 127 पैदल सेना बटालियन क्षेत्रीय सेना (गढ़वाल ईसीओ) भारतीय सेना रिजर्व : देहरादून, उत्तराखंड * गढ़वाल स्काउट (स्काउट बटालियन) के बा। * 14 राष्ट्रीय राइफल (गढ़ आरआईएफ) के बा। * 36 राष्ट्रीय राइफल्स (गढ़ आरआईएफ) द गैलेंट या द गैलेंट 36 * 48 राष्ट्रीय राइफल (गढ़ आरआईएफ) के बा। <ref name=":1"/> == लड़ाई आ रंगमंच के सम्मान == आज तक रेजिमेंट के 33 बैटल ऑनर मिलल बा। एहमें से सात गो के आजादी का बाद के दौर में दिहल गइल बा. रेजिमेंट के निम्नलिखित रंगमंच के सम्मान भी मिलल बा: जम्मू-कश्मीर - 1947-48, पंजाब - 1965, राजस्थान - 1965, पूर्वी पाकिस्तान - 1971, कारगिल - 1999। '''पहिला विश्वयुद्ध''' : "ला बासी 1914", "आर्मेन्टियर्स 1914", "फेस्टबर्ट 1914, 15", "न्यूव चैपल 1915", "ऑबर्स 1915", "फ्रांस आ फ्लैंडर्स 1914-15", "मिस्र 1915-16", " खान बगदादी 1918", "शर्कत 1918", "मेसोपोटामिया 1917-18", "मैसिडोनिया 1918"। 'अफगानिस्तान 1919' के बा। '''द्वितीय विश्वयुद्ध''' : "गलाबत 1940", "बरेंटू 1941", "केरेन 1941", "मासावा 1941", "अम्बा अलागी 1941", 'अबिसिनिया 1940-41', 'उत्तर अफ्रीका 1940-43', "कुआंटन", ' मलय 1941-42', "येनांगयुंग 1942", "मोनवा 1942", "उत्तर अरकन 1944", "नागाकिदाउक दर्रा 1944", "रामारी 1945", "तौंगुप 1945", "बर्मा 1942-45", "सिट्टा डी कैस्टेलो 1944 " ", ", 'इटली 1943-45' के बा। '''आजादी के बाद''' : "तिथवाल 1947-48", 'जम्मू कश्मीर 1947-48', "नूरंग 1962", "बतुर डोगरंडी 1965", 'पंजाब 1965', "गदरा रोड 1965", 'राजस्थान 1965', "हिली 1971 " ", 'पूर्वी पाकिस्तान 1971', 'ऑपरेशन पवन 1988', 'बटालिक 1999', 'द्रास 1999', 'कारगिल'। == सजावट == रेजिमेंट के श्रेय तीन विक्टोरिया क्रॉस, एक एसी, चार एमवीसी, 15 केसी, 52 वीआरसी, 49 एससी, 16 पीवीएसएम, चार यूवाईएसएम, 13 वाईएसएम, 350 एसएम (नौ बेर समेत), 25 एवीएसएम (एक बेर समेत) अउरी 51 के बा वीएसएम (नौ बेर समेत) के बा। '''सजावट (आजादी से पहिले) के बा।''' विक्टोरिया क्रॉस के प्राप्तकर्ता लोग के बा: * नायक दरवान सिंह नेगी - पहिला विश्वयुद्ध, फेस्टबर्ट - फ्रांस, 1914 * राइफलमैन गबर सिंह नेगी (मरणोत्तर) - पहिला विश्वयुद्ध, न्यूवे चैपल, 1915 * लेफ्टिनेंट विलियम डेविड केनी (मरण के बाद) - वजीरिस्तान अभियान, 1920 '''सजावट (आजादी के बाद) के बा।''' अशोक चक्र के प्राप्तकर्ता लोग : १. नाईक भवानी दत्त जोशी (मरणोपरांत), जून 1984, ऑपरेशन ब्लू स्टार, अमृतसर, भारत के सिख अलगाववादियन के खिलाफ अभियान के दौरान अपना हरकत खातिर * लेफ्टिनेंट कर्नल कमन सिंह, 1947-1948 के भारत-पाकिस्तान युद्ध। * लेफ्टिनेंट कर्नल बीएम भट्टाचार्य, चीन-भारत युद्ध, 1962 * राइफलमैन जसवंत सिंह रावत (मरण के बाद), चीन-भारत युद्ध, 1962 * कप्तान चंद्रनारायण सिंह, 1965 के भारत-पाकिस्तान युद्ध। वीर चक्र के प्राप्तकर्ता लोग : १. * राइफलमैन कुलदीप सिंह भंडारी द्वारा ऑपरेशन पवन, शांति सेना, 1988। * कप्तान जिंटू गोगोई, ऑपरेशन विजय, 1999 के बा * कर्नल (सेवानिवृत्त) तत्कालीन मेजर अजय कोठियाल 12 मई 2003 के कश्मीर के पुलवामा जिला में सर्जिकल स्ट्राइक के नेतृत्व कइले रहले। * सूबेदार अजय वर्धन के ह == रेजिमेंटल सेंटर - लैंसडाउन == लैंसडाउन, समुंद्र तल से 5,800 फीट (1,800 मीटर) के ऊँचाई पर, गढ़वाल राइफल्स के भर्ती केंद्र हवे। 1 अक्टूबर 1921 के रेजिमेंटल सेंटर आपन पहिला रेसिंग डे मनवलस। अब 1 अक्टूबर के बटालियन के रेसिंग डे के रूप में मनावल जाला। आजादी के बाद केंद्र के नाम बदल के गढ़वाल राइफल्स रेजिमेंटल सेंटर रखल गइल। प्रशिक्षण के दौरान कठोर अभ्यास हर रंगरूट में अनुशासन के भावना पैदा करे में मदद करेला। शारीरिक फिटनेस, मानसिक ताकत अउरी हथियार संभाले प खास जोर दिहल जाला। गढ़वाली के एगो युवक 34 सप्ताह के ट्रेनिंग कोर्स सफलतापूर्वक पूरा कइला के बाद फौजी में बदल जाला। एकरा बाद एह जवान के दू हफ्ता अउरी विद्रोह विरोधी अभियान के प्रशिक्षण दिहल जाला. == रेजिमेंट के कर्नल == * ब्रिगेडियर जेटी इवाट, डीएसओ (03.03.1914-24.03.19) के बा। * मेजर जनरल सर जेएचके स्टीवर्ट, केसीबी, डीएसओ (18.08.1939-30.11.1944) के बा। * लेफ्टिनेंट जनरल सर आरबी डीड्स, केसीबी, ओबीई, एमसी (01.12.1944-31.05.1949) के ह। * मेजर जनरल हीरा लाल अटल (01.06.1949-01.06.1959) के नाम से जानल जाला। * मेजर जनरल जी भरत सेवाक सिंह, एमसी (05.07.1959-31.10.1970) के बा। * मेजर जनरल एच एन सिंघल, पीवीएसएम, एवीएसएम (01.11.1970-31.12.1978) के बा। * लेफ्टिनेंट जनरल के महेंद्र सिंह, पीवीएसएम (24.04.1979-06.04.1988) के बा। * लेफ्टिनेंट जनरल आरवी कुलकर्णी, पीवीएसएम, यूवाईएसएम, एवीएसएम (07.04.1988-30.04.1993) के बा। * मेजर जनरल एस सोंधी (01.05.1993-01.11.1994) के लिखल बा। * मेजर जनरल एसपीएस कंवर, एवीएसएम, वीएसएम (02.11.1994-31.07.1996) के बा। * ब्रिगेडियर एआईएस ढिलोन, वीएसएम (01.08.1996 - 31.07.1997) के बा। * ब्रिगेडियर जगमोहन रावत (01.08.1997-30.09.2000) के बा। * मेजर जनरल एडब्ल्यू रणभिसे, एवीएसएम, एसएम (01.10.2000-28.02.2003) के बा। * लेफ्टिनेंट जनरल एमसी भंडारी, पीवीएसएम, एवीएसएम (01.03.2003-31.08.2006) के बा। * लेफ्टिनेंट जनरल परमजीत सिंह, पीवीएसएम, एवीएसएम, वीएसएम (01.09.2006 - 31.05.2008) के बा। * लेफ्टिनेंट जनरल बीके चेंगापा, पीवीएसएम, एवीएसएम (02.06.2008-31.12.2010) के बा। * लेफ्टिनेंट जनरल सैयद अता हसनैन, पीवीएसएम, यूवाईएसएम, एवीएसएम, एसएम, वीएसएम** (01.01.2011-30.06.2013) के बा। * लेफ्टिनेंट जनरल सारथ चंद, पीवीएसएम, यूवाईएसएम एवीएसएम, वीएसएम, एडीसी (01.07.2013-04.05.2018) के बा। * लेफ्टिनेंट जनरल चेरिश मैथसन, पीवीएसएम, एसएम, वीएसएम (05.05.2018-04.08.2019) के बा। * लेफ्टिनेंट जनरल एसके उपाध्याय पीवीएसएम, एसएम, एवीएसएम, वीएसएम के बा * लेफ्टिनेंट जनरल एनएस राजा सुब्रमणि, एवीएसएम, एसएम के बा == सब देखल जाव == [[श्रेणी:भारतीय सेना]] [[श्रेणी:थल सेना]] a1nuesxwysgu3sc440133qmjvq6n52l नवीन एवं नवीकरणीय ऊर्जा मंत्रालय, भारत सरकार 0 90439 803332 768113 2026-07-29T16:21:30Z SM7Bot 7808 /* top */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803332 wikitext text/x-wiki {{Infobox government agency | name = नवीन एवं नवीकरणीय ऊर्जा मंत्रालय | native_name = अक्षय ऊर्जा मंत्रालय | native_name_a = | native_name_r = | type = | seal = MNRE India.svg | seal_size = 250px | seal_caption = | logo = | logo_size = | logo_caption = | image = Raj Kumar Singh with PM Modi (cropped).jpg | image_size = | image_caption = [[राज कुमार सिंह]] | formed = {{Start date and age|df=yes|1992}} | preceding1 = Department of Non-conventional Energy Sources (DNES) | preceding2 = | dissolved = | jurisdiction = [[भारत सरकार]] | headquarters = [[नई दिल्ली]], [[भारत]] | coordinates = <!--{{coord|LATITUDE|LONGITUDE|type:landmark_region:US|display=inline,title}}--> | motto = | employees = | budget = {{INRConvert|5146.63|c}} <small>(2018-19 est.)<ref>{{cite web |url=http://www.indiabudget.gov.in/ub2018-19/eb/sbe95.pdf |title=Budget data |date=2019 |website=www.indiabudget.gov.in |access-date=15 September 2018 |archive-url=https://web.archive.org/web/20180304170727/http://www.indiabudget.gov.in/ub2018-19/eb/sbe95.pdf |archive-date=4 March 2018 |url-status=dead }}</ref></small> | minister1_name = [[राज कुमार सिंह]] | minister1_pfo = केंद्रीय मंत्री | deputyminister1_name = भगवंत कुबा | deputyminister1_pfo = राज्यमंत्री | deputyminister2_name = | deputyminister2_pfo = | chief1_name = इंदु शेखर चतुर्वेदी भारतीय नौकरशाह | chief1_position = (सचिव) | chief2_name = | chief2_position = | chief3_name = | chief3_position = | chief4_name = | chief4_position = | chief5_name = | chief5_position = | chief6_name = | chief6_position = | chief7_name = | chief7_position = | chief8_name = | chief8_position = | chief9_name = | chief9_position = | parent_department = | parent_agency = | child1_agency = | child2_agency = | keydocument1 = | website = https://mnre.gov.in/ | map = | map_size = | map_caption = | footnotes = }} <references /> '''नया आ अक्षय ऊर्जा मंत्रालय''' ( '''MNRE''' ) [[भारत सरकार]] के एगो मंत्रालय हवे, जेकर नेतृत्व वर्तमान केंद्रीय कैबिनेट मंत्री [[राज कुमार सिंह]] करे लें, जे मुख्य रूप से अनुसंधान आ बिकास, बौद्धिक संपदा संरक्षण, आ अंतर्राष्ट्रीय सहयोग, संवर्धन, आ समन्वय के जिम्मेदारी लेले बा अक्षय ऊर्जा स्रोत जइसे कि पवन ऊर्जा, छोट हाइड्रो, बायोगैस, आ [[सौर ऊर्जा]] में . मंत्रालय के व्यापक उद्देश्य [[भारत]] के ऊर्जा के जरूरत के पूरक खातिर नया अउरी अक्षय ऊर्जा के विकास अउरी तैनाती कइल बा . मंत्रालय के वर्तमान सचिव आनंद कुमार बाड़े .<ref>{{Cite web |title=Anand Kumar joins MNRE as Secretary |url=http://www.energynext.in/2017/07/anand-kumar-joins-mnre-as-secretary/ |access-date=2019-06-23 |website=Energynext |language=en-US |archive-date=2019-06-20 |archive-url=https://web.archive.org/web/20190620114054/http://www.energynext.in/2017/07/anand-kumar-joins-mnre-as-secretary/ |url-status=dead }}<cite class="citation web cs1" data-ve-ignore="true">[http://www.energynext.in/2017/07/anand-kumar-joins-mnre-as-secretary/ "Anand Kumar joins MNRE as Secretary"] {{Webarchive|url=https://web.archive.org/web/20190620114054/http://www.energynext.in/2017/07/anand-kumar-joins-mnre-as-secretary/ |date=2019-06-20 }}. </cite></ref> मंत्रालय के मुख्यालय [[नई दिल्ली|नई दिल्ली के]] लोधी रोड में बा .<ref>[http://www.mnre.gov.in/contact.htm Contact Details] {{Webarchive|url=https://web.archive.org/web/20110820015739/http://www.mnre.gov.in/contact.htm|date=20 August 2011}}</ref> मंत्रालय के 2016-17 के वार्षिक रिपोर्ट के मुताबिक, भारत कई गो अक्षय ऊर्जा क्षेत्र में काफी प्रगति कइले बा जवना में शामिल बा, [[सौर ऊर्जा]], पवन ऊर्जा, अउरी जलविद्युत । <ref>{{Cite web |title=Annual Report |url=http://mnre.gov.in/file-manager/annual-report/2016-2017/EN/index.html |access-date=2018-09-15 |publisher=Mnre.gov.in}}<cite class="citation web cs1" data-ve-ignore="true">[http://mnre.gov.in/file-manager/annual-report/2016-2017/EN/index.html "Annual Report"]. </cite></ref> ==संदर्भ== {{Reflist}} qcirn4tkxhp5rpjw7edf5m210yxkm5d बाबा अजगैबीनाथ धाम 0 92013 803354 802694 2026-07-29T16:23:04Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803354 wikitext text/x-wiki {{Copy edit|for=फ़ॉर्मेटिंग, हिज्जे आ टोन}} '''अजबैगीनाथ मंदिर''' या '''अजगैवीनाथ धाम''' भारत के बिहार के [[भागलपुर जिला]] के सुल्तानगंज में स्थित हिन्दू लोग के एगो परसिद्ध मंदिर बा। हिन्दू लोग खातिर ई एगो महत्वपूर्ण तीर्थ स्थल हवे आ एकर धार्मिक महत्व बहुत बा। ई पवित्र [[गंगा नदी]] के किनारे बसल बा।{{Infobox Hindu temple|name=बाबा अजगैबीनाथ धाम|image=Ajgaibinath temple Day-View.png|caption=बाबा अजगैवीनाथ धाम|map_type=India Bihar|map_caption=Location in Bihar|coordinates={{coord|25|15|08.6|N|86|44|03.5|E|type:landmark_region:IN|display=inline,title}}|country=[[भारत]]|state=[[भारत]]|district=[[भागलपुर जिला]]|location=सुल्तानगंज, [[भागलपुर जिला]]|architecture=वैदिक-शैली|inscriptions=|creator=|temple_board=बाबा अजगैबीनाथ मंदिर समिति|website=}}{{हिंदू धर्म}} == धार्मिक महत्व के बा == '''अजगैबीनाथ मंदिर''' के '''मनकमना मंदिर''' के नाम से भी जानल जाला। भक्त लोग के कहनाम बा कि ए जगह प कइल इच्छा पूरा हो जाला। एह संबंध में एह मंदिर के महंत प्रेमानंद जी के अनुसार '''''"एह जगह के अइसन मान्यता बा कि एह जगह के आधिकारिक पूजा सबसे पहिले होला। एकरा बाद ही देवघर के महादेव के पूजा होला।"'''''<ref>{{Cite web |title=Ajgaivinath Temple, Bihar - Info, Timings, Photos, History |url=https://www.templepurohit.com/hindu-temple/ajgaivinath-temple/ |access-date=2023-07-27 |website=TemplePurohit - Your Spiritual Destination {{!}} Bhakti, Shraddha Aur Ashirwad |language=en-US }}{{Dead link|date=September 2025 |bot=InternetArchiveBot |fix-attempted=yes }}</ref> == देवता आ मान्यता के बारे में बतावल गइल बा == '''अजगैबीनाथ मंदिर''' में पूजल जाए वाला मुख्य देवता भगवान शिव हवें जिनके अजगैबीनाथ के नाँव से भी जानल जाला। हिन्दू पौराणिक कथा सभ के अनुसार ई मानल जाला कि [[शिव|भगवान शिव]] [[अमरनाथ गुफा मंदिर|अमरनाथ गुफा]] के यात्रा के दौरान कुछ समय ले इहाँ निवास कइले रहलें। मंदिर के ऐतिहासिक आ धार्मिक महत्व साल भर में खासतौर पर भगवान शिव के समर्पित विशेष अवसर आ परब के दौरान बहुत संख्या में भक्त लोग के आकर्षित करे ला।<ref>{{Cite web |last=Tourism |first=Bihar |date=30 January 2023 |title=Ajgaibinath Temple |url=https://tourism.bihar.gov.in/en/destinations/bhagalpur/ajgaibinath-temple |access-date=20 July 2023 |website=tourism.bihar.gov.in |archive-date=21 July 2023 |archive-url=https://web.archive.org/web/20230721141903/https://tourism.bihar.gov.in/en/destinations/bhagalpur/ajgaibinath-temple |url-status=dead }}</ref> == श्रावणी मेला अउर काँवर यात्रा == हर साल लाखों तीर्थयात्री एह तीर्थ में आवेले। ई श्रावण (हिन्दू कैलेंडर के एक महीना) के मेला खातिर परसिद्ध बा, जुलाई से अगस्त के बीच। भारत के विभिन्न भाग से लगभग 8 से 1 करोड़ श्रद्धालु लोग एह जगह पर आवेला आ अजगैबीनाथ मंदिर के किनारे सुल्तानगंज से एकट्ठा कइल देवता के गंडक के पवित्र जल ('''उत्तरवाहिनी गंगा''') चढ़ावेला आ [[शिव|'''अजगैबीनाथ (भगवान शिव)''']] के प्रार्थना करेला जवन लगभग... देवघर आ बैद्यनाथ से 108 किलोमीटर दूर बा। पानी भी कानवरिया लोग ले आवेला, जे कवड़ी में पानी के ढो के चलेला, आ पूरा दूरी, नंगे पांव चलेला। पूरा रास्ता पानी लेके चलत बड़हन भीड़ मिली। केसर से रंगल कपड़ा में लोग के अटूट लाइन महीना भर पूरा 108 किलोमीटर में फइलल बा। तीर्थयात्री लोग के डाक बम कहल जाला आ [[भागलपुर जिला]] में स्थित अजगैबीनाथ , सुल्तानगुंज से '''वैद्यनाथ''' के यात्रा में एक बेर भी ना रुकेले . बाद में मंदिर के तीर्थयात्री लोग '''बासुकीनाथ मंदिर''' के दौरा करेला।<ref>{{Cite web |date=2011-08-11 |title=Administration gears up for Shravani Fair - Times Of India |url=http://articles.timesofindia.indiatimes.com/2004-05-30/patna/28348050_1_shrawani-bhagalpur-high-level-meeting |access-date=2023-07-22 |website=web.archive.org |archive-date=2011-08-11 |archive-url=https://web.archive.org/web/20110811055452/http://articles.timesofindia.indiatimes.com/2004-05-30/patna/28348050_1_shrawani-bhagalpur-high-level-meeting |url-status=dead }}</ref><ref>{{Cite web |date=2011-08-11 |title=Month-long Shrawani Mela ends - Times Of India |url=http://articles.timesofindia.indiatimes.com/2009-08-06/ranchi/28156469_1_kumbh-mela-lakh-pilgrims-shrawani-mela |url-status=dead |access-date=2023-07-22 |website=web.archive.org |archive-date=2011-08-11 |archive-url=https://web.archive.org/web/20110811055512/http://articles.timesofindia.indiatimes.com/2009-08-06/ranchi/28156469_1_kumbh-mela-lakh-pilgrims-shrawani-mela }}</ref><ref>{{Cite web |date=2023-07-17 |title=PHOTOS: अजगैबीनाथ मंदिर में सरकारी पूजा की देखें तस्वीर, सोमवार को सुल्तानगंज में कांवरियों का दिखा हुजूम |url=https://www.prabhatkhabar.com/photos/shravani-mela-2023-somwari-puja-photos-of-ajgaibinath-mandir-sultanganj-kanwariya-path-skt |access-date=2023-07-22 |website=Prabhat Khabar |language=hi}}</ref> == गैलरी में बा == [[चित्र:Ajgaibinath_temple_Day-View.png|center|thumb|276x276px| अजगैबीनाथ मंदिर के दिन-दृश्य]] == इहो देखल जाव == * [[बाबा धाम]] * [[माँ जलेश्वरी मंदिर]] == संदर्भ दिहल गइल बा == {{Reflist}} [[श्रेणी:बिहार के पर्यटक आकर्षण]] [[श्रेणी:शिव मंदिर]] [[श्रेणी:बिहार के हिंदू मंदिर]] [[श्रेणी:हिंदू मंदिर]] nevs9ux9kmi213nuxq7d3s47fefynte 2024 के भारतीय आम चुनाव 0 92022 803373 785049 2026-07-29T16:24:37Z SM7Bot 7808 /* top */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803373 wikitext text/x-wiki {{Infobox election |country = भारत |type = संसदीय |election_name = भारतीय आम चुनाव,२०२४ |seats_for_election = लोक सभा की सभी 543 सीटे |previous_election = भारतीय आम चुनाव, 2019 |previous_year = २०१९ |next_election = भारतीय आम चुनाव, 2024 |next_year = २०२९ |election_date = अप्रैल-मई २०२४ |map_image = Wahlkreise in Indien.svg |map_caption = क्षेत्र के हिसाब से सीट। चुकी इ एफपीटीपी चुनाव ह एहसे सीट के योग हर पार्टी के कुल वोट हिस्सेदारी के अनुपात में ना तय कइल जाला, बालुक हर क्षेत्र में बहुलता के आधार प तय कइल जाला। |opinion_polls = <!--सन् २०२४ के भारतीय आम चुनाव से सम्बन्धित जनमत सर्वेक्षण--> |majority_seats = २७२ |previous_mps = १७वीं लोक सभा के सदस्यों की सूची |elected_mps = <!--१८वीं लोक सभा के सदस्यों की सूची--> |ongoing = नहीं |image1 = Official Photograph of Prime Minister Narendra Modi Portrait.png |leader1 = [[नरेंद्र मोदी]] |leaders_seat1 = [[वाराणसी लोक सभा निर्वाचन क्षेत्र|वाराणसी]] |seats1 = |party1 = [[भारतीय जनता पार्टी|भाजपा]] |alliance1 = [[राष्ट्रीय जनतांत्रिक गठबंधन|राजग]] |percentage1 = |last_election1 = ३०३ सीटें |popular_vote1 = |seat_change1 = |seats_before1 = |swing1 = |image2 = Mallikarjun Kharge briefing the media after presenting the Interim Railway Budget 2014-15 in New Delhi (cropped).jpg |leader2 = [[राहुल गांधी]] |leaders_seat2 = [[वायनाड लोक सभा निर्वाचन क्षेत्र|वायनाड]] |seats2 = |party2 = [[भारतीय राष्ट्रीय कांग्रेस|कांग्रेस]] |alliance2 = [[इंडियन नेशनल डेवलपमेंटल इंक्लूसिव अलायंस|इंनेडेइंअ]] |percentage2 = |last_election2 = ५२ सीटें |popular_vote2 = |seat_change2 = |seats_before2 = |swing2 = |before_party = [[भारतीय जनता पार्टी|भाजपा]] |before_election = [[नरेंद्र मोदी]] |title = [[भारत के परधानमंत्री|प्रधानमंत्री]] |after_election = |after_party = |posttitle = [[भारत के परधानमंत्री|प्रधानमंत्री]] चुनाव के बाद }} '''भारतीय आम चुनाव''' मई 2024 में 18वीं लोकसभा के सदस्य चुने के होवे वाला बा। वर्तमान प्रधानमंत्री भारतीय जनता पार्टी के नरेंद्र मोदी बाड़े। 1ex1up8elght58yq1a3ud86ft1b2dnj अँगुरियावल (सेक्स) 0 96889 803315 798946 2026-07-29T16:20:20Z SM7Bot 7808 /* सुरक्षा आ यौन उत्पीड़न */ टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803315 wikitext text/x-wiki '''अँगुरियावल''' चाहे '''अँगुरी कइल''' हाथ के [[अँगुरी]] के इस्तेमाल से [[योनि]] जेकरा के [[बुर]] भी कहल जाला या फिर [[गुदा]] के उत्तेजित करे के क्रिया ह। ई खुद से अकेलहूँ कइल जा सके ला आ पार्टनरो के संघे कइल जा सके ला। ==सुरक्षा आ यौन उत्पीड़न== आम तौर प अंगुरी कइल सुरक्षित सेक्स मानल जाला। बिना सहमति के यौन पैठ के कानूनी तौर प कइसे वर्गीकृत कइल जाला, इ अधिकार क्षेत्र प निर्भर करेला। उदाहरण खातिर, बिना सहमति के अँगुरी से योनि भा गुदा में घुसावल ऑस्ट्रेलिया में बलात्कार आ अमेरिका में जबरन बलात्कार हवे। स्कॉटलैंड में बलात्कार शब्द के इस्तेमाल खाली लिंग घुसे खातिर होला जबकि अँगुरी के घुसे से "पेनेट्रेटिव यौन उत्पीड़न" मानल जा सके ला। ==चित्र गैलरी== [[चित्र:Wiki-fingering.png|thumb|पुरुष द्वारा औरत के अँगुरी कइल।]] [[चित्र:Solo fingering.jpg|thumb|खुद से अँगुरी करत एगो औरत।]] [[श्रेणी:सेक्स]] [[श्रेणी:मानव कामुकता]] 7zx3xxn9u2nxouacvku1pvpcuin2xl4 गगन थापा 0 99931 803362 787434 2026-07-29T16:23:43Z SM7Bot 7808 टाइपो फिक्स कइल गइल using [[Project:AWB|AWB]] 803362 wikitext text/x-wiki {{Infobox officeholder | name = गगन थापा | image = File:Gagan_Kumar_Thapa.jpg | width = 200px | caption = गगन थापा | nickname = | birth_name = | birth_date = १६ जुलाई, १९४० | birth_place = | residence = | alma_mater = [[त्रि-चन्द्र कलेज|त्रिचन्द्र कलेज]] | office = [[नेपाली कांग्रेसके सभापति]] | termstart = १४ जनवरी, २०२६ | termend = | primeminister = <nowiki>[[सुशीला कार्की]</nowiki> | president = [[विद्यादेवी भण्डारी]] | predecessor = शेरबहादुर देउपा | successor = | office2 = सभासद काठमाडौँ निर्वाचन क्षेत्र ४ | predecessor2 = सुप्रभा घिमिरे | occupation = राजनीति | nationality = [[नेपाली]] | religion = [[हिन्दू धर्म|हिन्दू]] | spouse = डा. अन्जना केसी थापा | awards = | website = http://www.gaganthapa.com.np }} '''गगन थापा''' (पूरा नाम: ''गगन कुमार थापा'' ) [[नेपाली कांग्रेस]] के एगो राजनीतिज्ञ आ नेता बानी। एगो मुखर वक्ता थापा के राजनीतिक हलकन में [[नेपालके जनआन्दोलन २०६३|जनआन्दोलन-२]] में अपना भूमिका खातिर जानल जाला। <ref name="election.ujyaaloonline.com">http://election.ujyaaloonline.com/candidates/2249/Gagan-Thapa/ {{Webarchive|url=https://web.archive.org/web/20140802102322/http://election.ujyaaloonline.com/candidates/2249/Gagan-Thapa/|date=2014-08-02}}</ref> [https://english.onlinekhabar.com/gagan-thapa-nepali-congress-president.html नेपाली कांग्रेस के अध्यक्ष भी बानी। कांग्रेस के दूसरा विशेष महाधिवेशन के बाद उ कांग्रेस के अध्यक्ष बनले।] == पृष्ठभूमि == थापा के जनम २०३३ विक्रम सम्बत में काठमांडू के '''एगो प्रसूति अस्पताल''' में भइल रहे। उनकर माता - पिता '''महेन्द्र कुमार थापा''' आ '''रामेश्वरी थापा''' हवें । उनकर दादा [[सोलुखुम्बु जिला]] के खाल्पा गाँव के धनी मुखिया भीमराज थापा हवें । इनकर जनम कश्यप कुल के गोदर थापा कुल में भइल रहे। गगन के बचपन के कुछ समय [[सिंधुपाल्चोक जिला|सिंधुपालचौक जिला]] में बीतल । उनकर पिता महेन्द्र नेपाल विद्युत प्राधिकरण में इंजीनियर रहले । गगन के एगो बड़ भाई आ दू गो छोट भाई बाड़े। उनकर बड़ भाई पवन, जबकि छोट भाई सुमन आ जीवन। गगन सिद्धार्थ बनस्थली हाई स्कूल में पढ़ाई कइले रहले. ओहिजा पढ़त घरी उनकर इच्छा रहे कि डाक्टर-इंजीनियर बन जाव. उनकर परिवार आ स्कूल में उहे पढ़ावल जात रहे। गगन 2046 बीएस के जन आंदोलन में सक्रिय हो गए। ओह घरी उ ग्रेड 9 में पढ़त रहले, हालांकि राजनीति में आवे के कवनो इरादा ना रहे। दूसरा संविधानसभा में कृषि आ जल संसाधन समिति के अध्यक्ष के रूप में प्रभावशाली भूमिका निभावे वाला थापा नेपाली कांग्रेस के सदस्य के रूप में हमेशा चर्चा में सबसे ऊपर रहे। समाजशास्त्र आ मानवशास्त्र में स्नातकोत्तर के पढ़ाई कइल थापा २०७८ से २०८२ ले नेपाली कांग्रेस के '''महासचिव''' के रूप में काम कइली। महा अधिवेशन में [[नेपाली कांग्रेस]] के प्रतिनिधि थापा के एगो अइसन नवही के रूप में जानल जाला जे राजा के सक्रिय शासन काल में कांग्रेस पार्टी के आवाज मजबूती से उठवले रहलें। दर्जनों बेर हिरासत में लिहल थापा सिनेमा देखल, पढ़ाई, घूमे-फिरे के शौक ह, बतवली। संविधान सभा में नेपाली कांग्रेस किताब के लेखक थापा के बियाह नेपाली कांग्रेस नेता अर्जुन नरसिंह के सी के बेटी '''डॉ.''' '''अंजना के.सी.''' थापा संविधानसभा में दलन के बीच सहमति बनावे के मुख्य प्रयास होई बतवनी । == राजनीति में प्रवेश करीं == [[चित्र:Gagan_Thapa_performing_speech_in_Sydney.jpg|thumb|गगन थापा]] त्रिचंद्र के कैंपस में पढ़ाई शुरू भईला के बाद उ छात्र संगठन में शामिल हो गइले। उ एनईएफ के सदस्य रहले। 2051 में त्रिचंद्र स्वतंत्र विद्यार्थी संघ के सदस्य चुनल गइलें। एकरा बाद उ राजनीति के माध्यम से आगे बढ़े के फैसला कइले। त्रिचंद्र सदस्य सचिव रहला के दौरान विद्यार्थी यूनियन के अध्यक्ष बनलें। 2055 बीएस में विद्यार्थी यूनियनके अध्यक्ष बनला के बाद उ एनईएफ के केंद्रीय राजनीति में शामिल हो गइले। उपाध्यक्ष आ महासचिव बन गइलन. 2051 में हमरा के जिनगी में पहिला बेर गिरफ्तार कइल गइल रहे। ओह घरी एमाले अल्पसंख्यक सरकार में रहे। गृहमंत्री केपी ओली मौजूद रहे। नेपाल विद्यार्थी संघ नेपाल बंद के आह्वान कइले रहे। आठ दस बेर जेल में बंद हो गइलन। 18 अक्टूबर 2059 के बाद जब सूर्य बहादुर थापा प्रधानमंत्री बनले त गुरुराज घिमिरे अवुरी पुरुषोत्तम पौडेल प भ्रष्टाचार के आरोप लागल। एकरा बाद उ आपन अधिकांश समय गली के आंदोलन में बितवले। 19 जनवरी के बाद गगन के देशद्रोह के आरोप में रिहा क दिहल गइल। ओकरा के गिरफ्तार क के छोड़ दिहल गइल। लोकतांत्रिक आंदोलन में उनकर मजबूत वक्तृत्व आ लोकप्रिय छवि के बहुते तारीफ भइल. हालांकि उ लोकतंत्र के नायक बने में सफल भईले। पार्टी के तेरहवाँ महासम्मेलन मे महासचिव पद खातिर उम्मेदवारी के घोषणा करेवाला थापा २०८२ मे भइल विशेष महाधिवेसनसे पार्टी सभापति निर्वाचित भ गइल बा । प्रचंड मंत्रिमंडल में स्वास्थ्य मंत्री बनले, 2073 बीएस . == शादीशुदा जीवन के बा == उनकर पत्नी कांग्रेस नेता अर्जुन नरसिंह केसी के बेटी [[डा अञ्जना केसी|डॉ. अंजना केसी]] हई। == प्रतिनिधि सभा के ह == २०७९ मे भइल प्रतिनिधि सभा के चुनाव में [[नेपाली कांग्रेस]] 21,302 <ref name="election.ujyaaloonline.com"/> वोट हासिल क के [[काठमांडो|काठमांडू के]] [[बागमती प्रदेश|बागमती प्रांत]] के प्रतिनिधि सभा क्षेत्र नंबर 4 में जीत हासिल कइलस। उनुकर निकटतम प्रतिद्वंद्वी नेपाल कम्युनिस्ट पार्टी के [[राजन भट्टराई]] १३,८५५ वोट हासिल कइले। <ref>{{Cite web |last=bizmandu.com |title=कांग्रेसमा गगन बने जित निकाल्ने पहिलो पदाधिकारी, 'देश हाँक्न नयाँ जोशका साथ लाग्छु' |url=https://www.bizmandu.com/content/20221122120446.html |access-date=2022-11-22 |website=www.bizmandu.com}}</ref> [[नेपाली कांग्रेस]] संसदीय दल के नेतृत्व खातिर [[शेरबहादुर देउवा|शेरबहादुर देउबा]] से 25 वोट से हार गइलें। <ref>{{Cite web |title=उम्मेदवारी दर्ता गराउन देउवा सिंहदरबारमा |url=https://annapurnapost.com/news/deuba-singh-darbar-to-register-candidacy-for-the-leader-of-the-parliamentary-party-220750 |access-date=2022-12-21 |website=उम्मेदवारी दर्ता गराउन देउवा सिंहदरबारमा |language=en}}</ref> थापा प्रधानमंत्री बने के मकसद से मिशन पीएम अभियान चलावत आइल बाड़ें। == संदर्भ दिहल गइल बा == {{Reflist}} [[श्रेणी:नेपाल के राजनेता]] [[श्रेणी:नेपाल के पूर्वमन्त्री]] [[श्रेणी:नेपाल के राजनीति]] 72wowh2ut4igqosn2bz3qjql13n8xft Module:InfoboxImage/data 828 100982 803571 2025-10-18T02:04:40Z en>Hike395 0 factor out data 803571 Scribunto text/plain local placeholder_image = { "Blue - Replace this image female.svg", "Blue - Replace this image male.svg", "Flag of None (square).svg", "Flag of None.svg", "Flag of.svg", "Green - Replace this image female.svg", "Green - Replace this image male.svg", "Image is needed female.svg", "Image is needed male.svg", "Location map of None.svg", "Male no free image yet.png", "Missing flag.png", "No flag.svg", "No free portrait.svg", "No portrait (female).svg", "No portrait (male).svg", "Red - Replace this image female.svg", "Red - Replace this image male.svg", "Replace this image female.svg", "Replace this image male (blue).svg", "Replace this image male.svg", "Silver - Replace this image female.svg", "Silver - Replace this image male.svg", "Replace this image.svg", "Cricket no pic.png", "CarersLogo.gif", "Diagram Needed.svg", "Example.jpg", "Image placeholder.png", "No male portrait.svg", "Nocover-upload.png", "NoDVDcover copy.png", "Noribbon.svg", "No portrait-BFD-test.svg", "Placeholder barnstar ribbon.png", "Project Trains no image.png", "Image-request.png", "Sin bandera.svg", "Sin escudo.svg", "Replace this image - temple.png", "Replace this image butterfly.png", "Replace this image.svg", "Replace this image1.svg", "Resolution angle.png", "Image-No portrait-text-BFD-test.svg", "Insert image here.svg", "No image available.png", "NO IMAGE YET square.png", "NO IMAGE YET.png", "No Photo Available.svg", "No Screenshot.svg", "No-image-available.jpg", "Null.png", "PictureNeeded.gif", "Place holder.jpg", "Unbenannt.JPG", "UploadACopyrightFreeImage.svg", "UploadAnImage.gif", "UploadAnImage.svg", "UploadAnImageShort.svg", "CarersLogo.gif", "Diagram Needed.svg", "No male portrait.svg", "NoDVDcover copy.png", "Placeholder barnstar ribbon.png", "Project Trains no image.png", "Image-request.png", "Noimage.gif", } local categories = { url_image_links = "[[Category:Pages using infoboxes with URL in image parameter]]", thumbnail_images = "[[Category:Pages using infoboxes with thumbnail images]]", } return { placeholder_image = placeholder_image, categories = categories } aiyqe7ro9krvvm7ujhmh89pgy3x0il5 803572 803571 2025-10-18T02:15:57Z en>Hike395 0 return hash table not list 803572 Scribunto text/plain local placeholderList = { "Blue - Replace this image female.svg", "Blue - Replace this image male.svg", "Flag of None (square).svg", "Flag of None.svg", "Flag of.svg", "Green - Replace this image female.svg", "Green - Replace this image male.svg", "Image is needed female.svg", "Image is needed male.svg", "Location map of None.svg", "Male no free image yet.png", "Missing flag.png", "No flag.svg", "No free portrait.svg", "No portrait (female).svg", "No portrait (male).svg", "Red - Replace this image female.svg", "Red - Replace this image male.svg", "Replace this image female.svg", "Replace this image male (blue).svg", "Replace this image male.svg", "Silver - Replace this image female.svg", "Silver - Replace this image male.svg", "Replace this image.svg", "Cricket no pic.png", "CarersLogo.gif", "Diagram Needed.svg", "Example.jpg", "Image placeholder.png", "No male portrait.svg", "Nocover-upload.png", "NoDVDcover copy.png", "Noribbon.svg", "No portrait-BFD-test.svg", "Placeholder barnstar ribbon.png", "Project Trains no image.png", "Image-request.png", "Sin bandera.svg", "Sin escudo.svg", "Replace this image - temple.png", "Replace this image butterfly.png", "Replace this image.svg", "Replace this image1.svg", "Resolution angle.png", "Image-No portrait-text-BFD-test.svg", "Insert image here.svg", "No image available.png", "NO IMAGE YET square.png", "NO IMAGE YET.png", "No Photo Available.svg", "No Screenshot.svg", "No-image-available.jpg", "Null.png", "PictureNeeded.gif", "Place holder.jpg", "Unbenannt.JPG", "UploadACopyrightFreeImage.svg", "UploadAnImage.gif", "UploadAnImage.svg", "UploadAnImageShort.svg", "CarersLogo.gif", "Diagram Needed.svg", "No male portrait.svg", "NoDVDcover copy.png", "Placeholder barnstar ribbon.png", "Project Trains no image.png", "Image-request.png", "Noimage.gif", } local placeholder_image = {} for _, val in ipairs(placeholderList) do placeholder_image[val] = true end local categories = { url_image_links = "[[Category:Pages using infoboxes with URL in image parameter]]", thumbnail_images = "[[Category:Pages using infoboxes with thumbnail images]]", } return { placeholder_image = placeholder_image, categories = categories } 4h39dpz9m6jxmu0vznaup0kyz6rbh4y 803573 803572 2025-10-18T09:43:07Z en>WOSlinker 0 Protected "[[Module:InfoboxImage/data]]": [[WP:High-risk templates|High-risk template or module]] ([Edit=Require template editor access] (indefinite) [Move=Require template editor access] (indefinite)) 803572 Scribunto text/plain local placeholderList = { "Blue - Replace this image female.svg", "Blue - Replace this image male.svg", "Flag of None (square).svg", "Flag of None.svg", "Flag of.svg", "Green - Replace this image female.svg", "Green - Replace this image male.svg", "Image is needed female.svg", "Image is needed male.svg", "Location map of None.svg", "Male no free image yet.png", "Missing flag.png", "No flag.svg", "No free portrait.svg", "No portrait (female).svg", "No portrait (male).svg", "Red - Replace this image female.svg", "Red - Replace this image male.svg", "Replace this image female.svg", "Replace this image male (blue).svg", "Replace this image male.svg", "Silver - Replace this image female.svg", "Silver - Replace this image male.svg", "Replace this image.svg", "Cricket no pic.png", "CarersLogo.gif", "Diagram Needed.svg", "Example.jpg", "Image placeholder.png", "No male portrait.svg", "Nocover-upload.png", "NoDVDcover copy.png", "Noribbon.svg", "No portrait-BFD-test.svg", "Placeholder barnstar ribbon.png", "Project Trains no image.png", "Image-request.png", "Sin bandera.svg", "Sin escudo.svg", "Replace this image - temple.png", "Replace this image butterfly.png", "Replace this image.svg", "Replace this image1.svg", "Resolution angle.png", "Image-No portrait-text-BFD-test.svg", "Insert image here.svg", "No image available.png", "NO IMAGE YET square.png", "NO IMAGE YET.png", "No Photo Available.svg", "No Screenshot.svg", "No-image-available.jpg", "Null.png", "PictureNeeded.gif", "Place holder.jpg", "Unbenannt.JPG", "UploadACopyrightFreeImage.svg", "UploadAnImage.gif", "UploadAnImage.svg", "UploadAnImageShort.svg", "CarersLogo.gif", "Diagram Needed.svg", "No male portrait.svg", "NoDVDcover copy.png", "Placeholder barnstar ribbon.png", "Project Trains no image.png", "Image-request.png", "Noimage.gif", } local placeholder_image = {} for _, val in ipairs(placeholderList) do placeholder_image[val] = true end local categories = { url_image_links = "[[Category:Pages using infoboxes with URL in image parameter]]", thumbnail_images = "[[Category:Pages using infoboxes with thumbnail images]]", } return { placeholder_image = placeholder_image, categories = categories } 4h39dpz9m6jxmu0vznaup0kyz6rbh4y 803574 796827 2026-07-30T00:18:12Z SM7 3953 3 revisions imported from [[:en:Module:InfoboxImage/data]] 796826 Scribunto text/plain local placeholderList = { "Blue - Replace this image female.svg", "Blue - Replace this image male.svg", "Flag of None (square).svg", "Flag of None.svg", "Flag of.svg", "Green - Replace this image female.svg", "Green - Replace this image male.svg", "Image is needed female.svg", "Image is needed male.svg", "Location map of None.svg", "Male no free image yet.png", "Missing flag.png", "No flag.svg", "No free portrait.svg", "No portrait (female).svg", "No portrait (male).svg", "Red - Replace this image female.svg", "Red - Replace this image male.svg", "Replace this image female.svg", "Replace this image male (blue).svg", "Replace this image male.svg", "Silver - Replace this image female.svg", "Silver - Replace this image male.svg", "Replace this image.svg", "Cricket no pic.png", "CarersLogo.gif", "Diagram Needed.svg", "Example.jpg", "Image placeholder.png", "No male portrait.svg", "Nocover-upload.png", "NoDVDcover copy.png", "Noribbon.svg", "No portrait-BFD-test.svg", "Placeholder barnstar ribbon.png", "Project Trains no image.png", "Image-request.png", "Sin bandera.svg", "Sin escudo.svg", "Replace this image - temple.png", "Replace this image butterfly.png", "Replace this image.svg", "Replace this image1.svg", "Resolution angle.png", "Image-No portrait-text-BFD-test.svg", "Insert image here.svg", "No image available.png", "NO IMAGE YET square.png", "NO IMAGE YET.png", "No Photo Available.svg", "No Screenshot.svg", "No-image-available.jpg", "Null.png", "PictureNeeded.gif", "Place holder.jpg", "Unbenannt.JPG", "UploadACopyrightFreeImage.svg", "UploadAnImage.gif", "UploadAnImage.svg", "UploadAnImageShort.svg", "CarersLogo.gif", "Diagram Needed.svg", "No male portrait.svg", "NoDVDcover copy.png", "Placeholder barnstar ribbon.png", "Project Trains no image.png", "Image-request.png", "Noimage.gif", } local placeholder_image = {} for _, val in ipairs(placeholderList) do placeholder_image[val] = true end local categories = { url_image_links = "[[Category:Pages using infoboxes with URL in image parameter]]", thumbnail_images = "[[Category:Pages using infoboxes with thumbnail images]]", } return { placeholder_image = placeholder_image, categories = categories } 4h39dpz9m6jxmu0vznaup0kyz6rbh4y विकिपीडिया:आँकड़ा सभ/२०२६/जुलाई 4 101100 803396 803262 2026-07-29T19:21:32Z NeechalBOT 7874 statistics 803396 wikitext text/x-wiki <!--- stats starts--->{{प्रयोगकर्ता:Neechalkaran/statnotice}}{| class="wikitable sortable" style="width:90%" |- ! Date(Time) ! Pages ! Articles ! Edits ! Users ! Files ! Activeusers {{User:Neechalkaran/template/daily |Date =१-७-२०२६ |Pages = 81892 |dPages = 4 |Articles = 9097 |dArticles = 0 |Edits = 795873 |dEdits = 9 |Files = 54 |dFiles = 0 |Users = 40208 |dUsers = 5 |Ausers = 57 |dAusers = -4 }} {{User:Neechalkaran/template/daily |Date =२-७-२०२६ |Pages = 81895 |dPages = 3 |Articles = 9097 |dArticles = 0 |Edits = 795882 |dEdits = 9 |Files = 54 |dFiles = 0 |Users = 40218 |dUsers = 10 |Ausers = 57 |dAusers = 0 }} {{User:Neechalkaran/template/daily |Date =३-७-२०२६ |Pages = 81895 |dPages = 0 |Articles = 9097 |dArticles = 0 |Edits = 795890 |dEdits = 8 |Files = 54 |dFiles = 0 |Users = 40227 |dUsers = 9 |Ausers = 57 |dAusers = 0 }} {{User:Neechalkaran/template/daily |Date =४-७-२०२६ |Pages = 81895 |dPages = 0 |Articles = 9096 |dArticles = -1 |Edits = 795904 |dEdits = 14 |Files = 54 |dFiles = 0 |Users = 40231 |dUsers = 4 |Ausers = 59 |dAusers = 2 }} {{User:Neechalkaran/template/daily |Date =५-७-२०२६ |Pages = 81902 |dPages = 7 |Articles = 9098 |dArticles = 2 |Edits = 795944 |dEdits = 40 |Files = 54 |dFiles = 0 |Users = 40236 |dUsers = 5 |Ausers = 59 |dAusers = 0 }} {{User:Neechalkaran/template/daily |Date =६-७-२०२६ |Pages = 81907 |dPages = 5 |Articles = 9101 |dArticles = 3 |Edits = 796019 |dEdits = 75 |Files = 54 |dFiles = 0 |Users = 40240 |dUsers = 4 |Ausers = 59 |dAusers = 0 }} {{User:Neechalkaran/template/daily |Date =७-७-२०२६ |Pages = 81916 |dPages = 9 |Articles = 9103 |dArticles = 2 |Edits = 796069 |dEdits = 50 |Files = 54 |dFiles = 0 |Users = 40248 |dUsers = 8 |Ausers = 63 |dAusers = 4 }} {{User:Neechalkaran/template/daily |Date =८-७-२०२६ |Pages = 81920 |dPages = 4 |Articles = 9104 |dArticles = 1 |Edits = 796087 |dEdits = 18 |Files = 54 |dFiles = 0 |Users = 40255 |dUsers = 7 |Ausers = 63 |dAusers = 0 }} {{User:Neechalkaran/template/daily |Date =९-७-२०२६ |Pages = 81925 |dPages = 5 |Articles = 9106 |dArticles = 2 |Edits = 796126 |dEdits = 39 |Files = 54 |dFiles = 0 |Users = 40261 |dUsers = 6 |Ausers = 63 |dAusers = 0 }} {{User:Neechalkaran/template/daily |Date =१०-७-२०२६ |Pages = 81928 |dPages = 3 |Articles = 9109 |dArticles = 3 |Edits = 796172 |dEdits = 46 |Files = 54 |dFiles = 0 |Users = 40265 |dUsers = 4 |Ausers = 64 |dAusers = 1 }} {{User:Neechalkaran/template/daily |Date =११-७-२०२६ |Pages = 81931 |dPages = 3 |Articles = 9109 |dArticles = 0 |Edits = 796343 |dEdits = 171 |Files = 54 |dFiles = 0 |Users = 40270 |dUsers = 5 |Ausers = 64 |dAusers = 0 }} {{User:Neechalkaran/template/daily |Date =१२-७-२०२६ |Pages = 81937 |dPages = 6 |Articles = 9112 |dArticles = 3 |Edits = 796450 |dEdits = 107 |Files = 54 |dFiles = 0 |Users = 40277 |dUsers = 7 |Ausers = 64 |dAusers = 0 }} {{User:Neechalkaran/template/daily |Date =१३-७-२०२६ |Pages = 81962 |dPages = 25 |Articles = 9118 |dArticles = 6 |Edits = 796848 |dEdits = 398 |Files = 54 |dFiles = 0 |Users = 40283 |dUsers = 6 |Ausers = 66 |dAusers = 2 }} {{User:Neechalkaran/template/daily |Date =१४-७-२०२६ |Pages = 81963 |dPages = 1 |Articles = 9118 |dArticles = 0 |Edits = 796951 |dEdits = 103 |Files = 54 |dFiles = 0 |Users = 40292 |dUsers = 9 |Ausers = 66 |dAusers = 0 }} {{User:Neechalkaran/template/daily |Date =१५-७-२०२६ |Pages = 81966 |dPages = 3 |Articles = 9119 |dArticles = 1 |Edits = 796982 |dEdits = 31 |Files = 54 |dFiles = 0 |Users = 40295 |dUsers = 3 |Ausers = 66 |dAusers = 0 }} {{User:Neechalkaran/template/daily |Date =१६-७-२०२६ |Pages = 81976 |dPages = 10 |Articles = 9121 |dArticles = 2 |Edits = 797197 |dEdits = 215 |Files = 54 |dFiles = 0 |Users = 40297 |dUsers = 2 |Ausers = 66 |dAusers = 0 }} {{User:Neechalkaran/template/daily |Date =१७-७-२०२६ |Pages = 81994 |dPages = 18 |Articles = 9126 |dArticles = 5 |Edits = 797326 |dEdits = 129 |Files = 54 |dFiles = 0 |Users = 40301 |dUsers = 4 |Ausers = 66 |dAusers = 0 }} {{User:Neechalkaran/template/daily |Date =१८-७-२०२६ |Pages = 82002 |dPages = 8 |Articles = 9128 |dArticles = 2 |Edits = 797384 |dEdits = 58 |Files = 54 |dFiles = 0 |Users = 40305 |dUsers = 4 |Ausers = 66 |dAusers = 0 }} {{User:Neechalkaran/template/daily |Date =१९-७-२०२६ |Pages = 82011 |dPages = 9 |Articles = 9129 |dArticles = 1 |Edits = 797820 |dEdits = 436 |Files = 54 |dFiles = 0 |Users = 40309 |dUsers = 4 |Ausers = 45 |dAusers = -21 }} {{User:Neechalkaran/template/daily |Date =२०-७-२०२६ |Pages = 82025 |dPages = 14 |Articles = 9132 |dArticles = 3 |Edits = 798488 |dEdits = 668 |Files = 54 |dFiles = 0 |Users = 40310 |dUsers = 1 |Ausers = 45 |dAusers = 0 }} {{User:Neechalkaran/template/daily |Date =२१-७-२०२६ |Pages = 82026 |dPages = 1 |Articles = 9133 |dArticles = 1 |Edits = 798514 |dEdits = 26 |Files = 54 |dFiles = 0 |Users = 40315 |dUsers = 5 |Ausers = 45 |dAusers = 0 }} {{User:Neechalkaran/template/daily |Date =२२-७-२०२६ |Pages = 82037 |dPages = 11 |Articles = 9138 |dArticles = 5 |Edits = 798566 |dEdits = 52 |Files = 54 |dFiles = 0 |Users = 40323 |dUsers = 8 |Ausers = 44 |dAusers = -1 }} {{User:Neechalkaran/template/daily |Date =२३-७-२०२६ |Pages = 82053 |dPages = 16 |Articles = 9143 |dArticles = 5 |Edits = 798618 |dEdits = 52 |Files = 54 |dFiles = 0 |Users = 40326 |dUsers = 3 |Ausers = 44 |dAusers = 0 }} {{User:Neechalkaran/template/daily |Date =२४-७-२०२६ |Pages = 82054 |dPages = 1 |Articles = 9143 |dArticles = 0 |Edits = 798630 |dEdits = 12 |Files = 54 |dFiles = 0 |Users = 40330 |dUsers = 4 |Ausers = 44 |dAusers = 0 }} {{User:Neechalkaran/template/daily |Date =२५-७-२०२६ |Pages = 82084 |dPages = 30 |Articles = 9146 |dArticles = 3 |Edits = 799457 |dEdits = 827 |Files = 54 |dFiles = 0 |Users = 40336 |dUsers = 6 |Ausers = 45 |dAusers = 1 }} {{User:Neechalkaran/template/daily |Date =२६-७-२०२६ |Pages = 82099 |dPages = 15 |Articles = 9152 |dArticles = 6 |Edits = 799564 |dEdits = 107 |Files = 54 |dFiles = 0 |Users = 40346 |dUsers = 10 |Ausers = 45 |dAusers = 0 }} {{User:Neechalkaran/template/daily |Date =२७-७-२०२६ |Pages = 82101 |dPages = 2 |Articles = 9152 |dArticles = 0 |Edits = 799603 |dEdits = 39 |Files = 54 |dFiles = 0 |Users = 40350 |dUsers = 4 |Ausers = 45 |dAusers = 0 }} {{User:Neechalkaran/template/daily |Date =२८-७-२०२६ |Pages = 82114 |dPages = 13 |Articles = 9156 |dArticles = 4 |Edits = 800027 |dEdits = 424 |Files = 54 |dFiles = 0 |Users = 40358 |dUsers = 8 |Ausers = 46 |dAusers = 1 }} {{User:Neechalkaran/template/daily |Date =२९-७-२०२६ |Pages = 82126 |dPages = 12 |Articles = 9160 |dArticles = 4 |Edits = 800303 |dEdits = 276 |Files = 54 |dFiles = 0 |Users = 40363 |dUsers = 5 |Ausers = 46 |dAusers = 0 }} <!---Place new stats here---> |} <!--- stats ends---> 7lknuqa9cxubzphcvurc99ih80nzpv8 Module:Deprecated 828 101239 803410 2025-03-21T01:53:59Z en>Awesome Aasim 0 module to mark modules or member items as deprecated 803410 Scribunto text/plain --- Marks functions as deprecated, and provides a warning when they are called. -- @release alpha -- @author [[User:Awesome_Aasim|Awesome Aasim]] -- @function deprecated -- @param {table} p package frame -- @param deprecatedTable -- @return package return function(p, deprecatedTable, replacement) local pckg = {} --- Warn -- @param {string} text warning text function warn(text) local tb = debug.traceback() mw.log(text .. '\n' .. tb) mw.addWarning(text .. tb:gsub("\n", "<br/>"):gsub("\t", "&emsp;")) end if deprecatedTable == nil or deprecatedTable == true then deprecatedTable = {} for k,_ in pairs(p) do deprecatedTable[k] = { deprecated = true, replacement = replacement or "" } end end setmetatable(pckg, { __index = function(t, index) if deprecatedTable[index] and deprecatedTable[index]["deprecated"] then warn( mw.ustring.format( "Deprecated member <code>%s</code> called ", index ) .. ( deprecatedTable[index]["replacement"] and mw.ustring.format("Please %s instead.", deprecatedTable[index]["replacement"]) or '' ) ) end return p[index] end }) return pckg end ssbisxzfz9d69ye8woy3nnh2smfoesa 803411 803410 2025-03-27T23:26:07Z en>Awesome Aasim 0 803411 Scribunto text/plain --- Marks functions as deprecated, and provides a warning when they are called. -- @release alpha -- @author [[User:Awesome_Aasim|Awesome Aasim]] -- @function deprecated -- @param {table} p package frame -- @param deprecatedTable -- @return package return function(p, deprecatedTable, replacement) local pckg = {} --- Warn -- @param {string} text warning text function warn(text) local tb = debug.traceback() mw.log(text .. '\n' .. tb) mw.addWarning(text .. tb:gsub("\n", "<br/>"):gsub("\t", "&emsp;")) end if deprecatedTable == nil or deprecatedTable == true then deprecatedTable = {} for k,_ in pairs(p) do deprecatedTable[k] = { deprecated = true, replacement = replacement or "" } end end setmetatable(pckg, { __index = function(t, index) if deprecatedTable[index] and deprecatedTable[index]["deprecated"] then warn( mw.ustring.format( "Deprecated member <code>%s</code> called. ", index ) .. ( deprecatedTable[index]["replacement"] and mw.ustring.format("Please %s instead.", deprecatedTable[index]["replacement"]) or '' ) ) end return p[index] end }) return pckg end r4g4p77ei7eef8z5ab5v120uucd4jkm 803412 803411 2025-03-30T15:49:00Z en>Awesome Aasim 0 803412 Scribunto text/plain --- Marks items as deprecated, and provides a warning when they are called. -- @release alpha -- @author [[User:Awesome_Aasim|Awesome Aasim]] -- @function deprecated -- @param {table} p package frame -- @param deprecatedTable -- @return package return function(p, deprecatedTable, replacement) local pckg = {} --- Warn -- @param {string} text warning text function warn(text) local tb = debug.traceback() mw.log(text .. '\n' .. tb) mw.addWarning(text .. tb:gsub("\n", "<br/>"):gsub("\t", "&emsp;")) end if deprecatedTable == nil or deprecatedTable == true then deprecatedTable = {} for k,_ in pairs(p) do deprecatedTable[k] = { deprecated = true, replacement = replacement or "" } end end setmetatable(pckg, { __index = function(t, index) if deprecatedTable[index] and deprecatedTable[index]["deprecated"] then warn( mw.ustring.format( "Deprecated member <code>%s</code> called. ", index ) .. ( deprecatedTable[index]["replacement"] and mw.ustring.format("Please %s instead.", deprecatedTable[index]["replacement"]) or '' ) ) end return p[index] end }) return pckg end 7ml5wv91a0i4zxlsitvw0bvoq1txcko 803413 801545 2025-05-27T19:00:29Z en>Awesome Aasim 0 803413 Scribunto text/plain --- Marks items as deprecated, and provides a warning when they are called. -- -- ## Limitations ## -- This module will not provide a warning if a deprecated item is called from -- within the same module that this module is used. -- -- @release alpha -- @author [[User:Awesome_Aasim|Awesome Aasim]] -- @function deprecated -- @param {table} p package frame -- @param deprecatedTable -- @return package return function(p, deprecatedTable, replacement) local pckg = {} --- Warn -- @param {string} text warning text function warn(text) local tb = debug.traceback() mw.log(text .. '\n' .. tb) mw.addWarning(text .. tb:gsub("\n", "<br/>"):gsub("\t", "&emsp;")) end if deprecatedTable == nil or deprecatedTable == true then deprecatedTable = {} for k,_ in pairs(p) do deprecatedTable[k] = { deprecated = true, replacement = replacement or "" } end end setmetatable(pckg, { __index = function(t, index) if deprecatedTable[index] and deprecatedTable[index]["deprecated"] then warn( mw.ustring.format( "Deprecated member <code>%s</code> called. ", index ) .. ( deprecatedTable[index]["replacement"] and mw.ustring.format("Please %s instead.", deprecatedTable[index]["replacement"]) or '' ) ) end return p[index] end }) return pckg end my1p1xp76qgfnvctx8pevv0cvz0hjyb 803414 801546 2026-07-29T23:43:35Z SM7 3953 4 revisions imported from [[:en:Module:Deprecated]] 801545 Scribunto text/plain --- Marks items as deprecated, and provides a warning when they are called. -- @release alpha -- @author [[User:Awesome_Aasim|Awesome Aasim]] -- @function deprecated -- @param {table} p package frame -- @param deprecatedTable -- @return package return function(p, deprecatedTable, replacement) local pckg = {} --- Warn -- @param {string} text warning text local function warn(text) local tb = debug.traceback() mw.log(text .. '\n' .. tb) mw.addWarning(text .. tb:gsub("\n", "<br/>"):gsub("\t", "&emsp;")) end if deprecatedTable == nil or deprecatedTable == true then deprecatedTable = {} for k,_ in pairs(p) do deprecatedTable[k] = { deprecated = true, replacement = replacement or "" } end end setmetatable(pckg, { __index = function(t, index) if deprecatedTable[index] and deprecatedTable[index]["deprecated"] then warn( mw.ustring.format( "Deprecated member <code>%s</code> called. ", index ) .. ( deprecatedTable[index]["replacement"] and mw.ustring.format("Please %s instead.", deprecatedTable[index]["replacement"]) or '' ) ) end return p[index] end }) return pckg end 022sv6wwuf8k0nkwfuwslws7f33h5rm प्रयोगकर्ता वार्ता:Roopeank 3 101339 803285 2026-07-29T13:26:34Z Ternarius 20240 Ternarius पन्ना [[प्रयोगकर्ता वार्ता:Roopeank]] के [[प्रयोगकर्ता वार्ता:LasseKeskinen]] पर स्थानांतरण कइलें: प्रयोगकर्ता के नाँव बदलाव के दौरान पन्ना "[[Special:CentralAuth/Roopeank|Roopeank]]" से "[[Special:CentralAuth/LasseKeskinen|LasseKeskinen]]" पर ऑटोमेटिक रूप से स्थानांतरित भइल। 803285 wikitext text/x-wiki #REDIRECT [[प्रयोगकर्ता वार्ता:LasseKeskinen]] 2vtlcb7aoiqgym2p7xzqwqwegq5vas9 अँजोर पाख 0 101340 803375 2026-07-29T17:39:40Z SM7 3953 पन्ना [[पाख]] पर अनुप्रेषित कइल गइल 803375 wikitext text/x-wiki #REDIRECT [[पाख]] g40w420yht93y905kz5oy3wc5k1bol2 803376 803375 2026-07-29T17:40:18Z SM7 3953 Added {{[[:Template:R to related topic|R to related topic]]}} tag to redirect 803376 wikitext text/x-wiki #REDIRECT [[पाख]] {{Redirect category shell| {{R to related topic}} }} 192loqddc2iljk6xfjdzz9hefcqrnsy श्रेणी:भारतीय हिंदू संत 14 101341 803377 2026-07-29T17:41:29Z SM7 3953 पन्ना बनावल गइल "[[श्रेणी:भारतीय संत|हिंदू]] [[श्रेणी:हिंदू संत]]" के साथ 803377 wikitext text/x-wiki [[श्रेणी:भारतीय संत|हिंदू]] [[श्रेणी:हिंदू संत]] r4n853xzhzm72snk21kgr8enbebzx0z 803378 803377 2026-07-29T17:42:46Z SM7 3953 +commonscat 803378 wikitext text/x-wiki {{cc|Hindu saints from India}} [[श्रेणी:भारतीय संत|हिंदू]] [[श्रेणी:हिंदू संत]] 3nii0muqci976gua0kg1nuhzwrrwd9d श्रेणी:भारतीय संत 14 101342 803379 2026-07-29T17:43:22Z SM7 3953 पन्ना बनावल गइल "[[श्रेणी:संत]]" के साथ 803379 wikitext text/x-wiki [[श्रेणी:संत]] jv43iok9fjy7qu4czush5hp9z0b88ia श्रेणी:संत 14 101343 803380 2026-07-29T17:44:34Z SM7 3953 पन्ना बनावल गइल "{{cc|Saints}} {{ctm}}" के साथ 803380 wikitext text/x-wiki {{cc|Saints}} {{ctm}} f0006dzlaeqinpcg8w28aj6jxywhs2m 803381 803380 2026-07-29T17:45:28Z SM7 3953 [[विकिपीडिया:हॉट-कैट|हॉट-कैट]] द्वारा [[श्रेणी:धर्म]] जोड़ल गइल 803381 wikitext text/x-wiki {{cc|Saints}} {{ctm}} [[श्रेणी:धर्म]] p2goxvzbsfkrwxqoksr13psbr9k0tgq श्रेणी:भारतीय हिंदू धार्मिक नेता 14 101344 803382 2026-07-29T17:46:05Z SM7 3953 पन्ना बनावल गइल "{{cc|Hindu religious leaders from India}}" के साथ 803382 wikitext text/x-wiki {{cc|Hindu religious leaders from India}} 72x9c5fzwofwbueoxqk6btyk9ai5j0t 803383 803382 2026-07-29T17:47:19Z SM7 3953 [[विकिपीडिया:हॉट-कैट|हॉट-कैट]] द्वारा [[श्रेणी:भारत में हिंदू धर्म]] जोड़ल गइल 803383 wikitext text/x-wiki {{cc|Hindu religious leaders from India}} [[श्रेणी:भारत में हिंदू धर्म]] pmopfpu62kni2dn2j7tps16m5m6gkp0 अन्हार पाख 0 101345 803385 2026-07-29T17:49:49Z SM7 3953 पन्ना [[पाख]] पर अनुप्रेषित कइल गइल 803385 wikitext text/x-wiki #REDIRECT [[पाख]] g40w420yht93y905kz5oy3wc5k1bol2 803386 803385 2026-07-29T17:50:04Z SM7 3953 Added {{[[:Template:R to related topic|R to related topic]]}} tag to redirect 803386 wikitext text/x-wiki #REDIRECT [[पाख]] {{Redirect category shell| {{R to related topic}} }} 192loqddc2iljk6xfjdzz9hefcqrnsy पौहारी बाबा 0 101346 803387 2026-07-29T18:03:23Z SM7 3953 नया आधार लेख / अंग्रेजी विकिपीडिया से अनुबाद कइ के 803387 wikitext text/x-wiki '''पौहारी बाबा''' (1798–1898), जिनका के '''पवहारी''' नाँव से भी जानल जाला, एगो हिंदू संन्यासी आ संत रहलें। उनकर जनम [[जौनपुर जिला|जौनपुर]] के प्रेमापुर गाँव में एगो ब्राह्मण परिवार में भइल रहे। बचपन में ऊ अपना चाचा के देखरेख में पढ़ाई करे खातिर [[गाजीपुर]] गइलें। उनकर चाचा [[रामानुज]] भा श्री संप्रदाय के अनुयायी रहलें। पढ़ाई पूरा कइला के बाद पौहारी बाबा कई गो जगह के यात्रा कइलें। [[काठियावाड़]] के [[गिरनार]] में उनकरा के [[योग]] के दीक्षा मिलल।<ref name="Nikhilananda p44—45">{{Harvnb|Nikhilananda|1953|pp=44–45}}</ref> ओकरा बाद ऊ गाजीपुर लवट अइलें आ अपना घरे में जमीन के नीचे एगो साधना-स्थल (गुफा जइसन आश्रम) बनवलें, जहाँ ऊ कई-कई दिन तक [[ध्यान]] आ योग के साधना करत रहलें। ऊ अपना नम्रता, विनम्र व्यवहार आ परोपकार के भावना खातिर जानल जात रहलें। कथा हवे कि, एक रात एगो चोर उनकर आश्रम में घुस गइल। जब चोर चोरी कइल सामान छोड़के भागे लागल, त नींद से जागला के बाद पौहारी बाबा ओकरा पाछे दौड़लें आ उल्टे ऊ सामान चोर के देवे के कोशिश कइलें। एह घटना के चोर पर एतना गहिर असर पड़ल कि बाद में ऊ संन्यासी बन गइल आ पौहारी बाबा के अनुयायी हो गइल। 1890 में [[स्वामी विवेकानंद]] गाजीपुर गइलें आ पौहारी बाबा से भेंट कइलें। [[सिस्टर निवेदिता]] के अनुसार, 1898 में पौहारी बाबा आग में जलके देह त्याग दिहलें। एह घटना के सामान्य रूप से आत्मदाह के रूप में देखल जाला।<ref name="Nikhilananda p44—45" /> {{clear}} == संदर्भ == {{Reflist|29em}} == स्रोत == * {{cite book|last=Nikhilananda|first=Swami|title=Vivekânanda: a biography|url=http://www.vivekananda.net/PDFBooks/BiographybyNikhilananda.pdf|year=1953|publisher=Ramakrishna-Vivekananda Center|isbn=978-0-911206-25-8}} sybw0vj5i8tsuq35f1jp59bsxbf9j07 803388 803387 2026-07-29T18:04:25Z SM7 3953 [[विकिपीडिया:हॉट-कैट|हॉट-कैट]] द्वारा +[[श्रेणी:भारतीय हिंदू संत]]; +[[श्रेणी:भारतीय हिंदू योगी]] 803388 wikitext text/x-wiki '''पौहारी बाबा''' (1798–1898), जिनका के '''पवहारी''' नाँव से भी जानल जाला, एगो हिंदू संन्यासी आ संत रहलें। उनकर जनम [[जौनपुर जिला|जौनपुर]] के प्रेमापुर गाँव में एगो ब्राह्मण परिवार में भइल रहे। बचपन में ऊ अपना चाचा के देखरेख में पढ़ाई करे खातिर [[गाजीपुर]] गइलें। उनकर चाचा [[रामानुज]] भा श्री संप्रदाय के अनुयायी रहलें। पढ़ाई पूरा कइला के बाद पौहारी बाबा कई गो जगह के यात्रा कइलें। [[काठियावाड़]] के [[गिरनार]] में उनकरा के [[योग]] के दीक्षा मिलल।<ref name="Nikhilananda p44—45">{{Harvnb|Nikhilananda|1953|pp=44–45}}</ref> ओकरा बाद ऊ गाजीपुर लवट अइलें आ अपना घरे में जमीन के नीचे एगो साधना-स्थल (गुफा जइसन आश्रम) बनवलें, जहाँ ऊ कई-कई दिन तक [[ध्यान]] आ योग के साधना करत रहलें। ऊ अपना नम्रता, विनम्र व्यवहार आ परोपकार के भावना खातिर जानल जात रहलें। कथा हवे कि, एक रात एगो चोर उनकर आश्रम में घुस गइल। जब चोर चोरी कइल सामान छोड़के भागे लागल, त नींद से जागला के बाद पौहारी बाबा ओकरा पाछे दौड़लें आ उल्टे ऊ सामान चोर के देवे के कोशिश कइलें। एह घटना के चोर पर एतना गहिर असर पड़ल कि बाद में ऊ संन्यासी बन गइल आ पौहारी बाबा के अनुयायी हो गइल। 1890 में [[स्वामी विवेकानंद]] गाजीपुर गइलें आ पौहारी बाबा से भेंट कइलें। [[सिस्टर निवेदिता]] के अनुसार, 1898 में पौहारी बाबा आग में जलके देह त्याग दिहलें। एह घटना के सामान्य रूप से आत्मदाह के रूप में देखल जाला।<ref name="Nikhilananda p44—45" /> {{clear}} == संदर्भ == {{Reflist|29em}} == स्रोत == * {{cite book|last=Nikhilananda|first=Swami|title=Vivekânanda: a biography|url=http://www.vivekananda.net/PDFBooks/BiographybyNikhilananda.pdf|year=1953|publisher=Ramakrishna-Vivekananda Center|isbn=978-0-911206-25-8}} [[श्रेणी:भारतीय हिंदू संत]] [[श्रेणी:भारतीय हिंदू योगी]] dxpydvgxkmhm6k9q776hbfhhoosm9ri 803389 803388 2026-07-29T18:10:18Z SM7 3953 [[User:SM7/stubsorter|Stubsorter]] के मदद से {{Bio-stub}} जोड़ल गइल। 803389 wikitext text/x-wiki '''पौहारी बाबा''' (1798–1898), जिनका के '''पवहारी''' नाँव से भी जानल जाला, एगो हिंदू संन्यासी आ संत रहलें। उनकर जनम [[जौनपुर जिला|जौनपुर]] के प्रेमापुर गाँव में एगो ब्राह्मण परिवार में भइल रहे। बचपन में ऊ अपना चाचा के देखरेख में पढ़ाई करे खातिर [[गाजीपुर]] गइलें। उनकर चाचा [[रामानुज]] भा श्री संप्रदाय के अनुयायी रहलें। पढ़ाई पूरा कइला के बाद पौहारी बाबा कई गो जगह के यात्रा कइलें। [[काठियावाड़]] के [[गिरनार]] में उनकरा के [[योग]] के दीक्षा मिलल।<ref name="Nikhilananda p44—45">{{Harvnb|Nikhilananda|1953|pp=44–45}}</ref> ओकरा बाद ऊ गाजीपुर लवट अइलें आ अपना घरे में जमीन के नीचे एगो साधना-स्थल (गुफा जइसन आश्रम) बनवलें, जहाँ ऊ कई-कई दिन तक [[ध्यान]] आ योग के साधना करत रहलें। ऊ अपना नम्रता, विनम्र व्यवहार आ परोपकार के भावना खातिर जानल जात रहलें। कथा हवे कि, एक रात एगो चोर उनकर आश्रम में घुस गइल। जब चोर चोरी कइल सामान छोड़के भागे लागल, त नींद से जागला के बाद पौहारी बाबा ओकरा पाछे दौड़लें आ उल्टे ऊ सामान चोर के देवे के कोशिश कइलें। एह घटना के चोर पर एतना गहिर असर पड़ल कि बाद में ऊ संन्यासी बन गइल आ पौहारी बाबा के अनुयायी हो गइल। 1890 में [[स्वामी विवेकानंद]] गाजीपुर गइलें आ पौहारी बाबा से भेंट कइलें। [[सिस्टर निवेदिता]] के अनुसार, 1898 में पौहारी बाबा आग में जलके देह त्याग दिहलें। एह घटना के सामान्य रूप से आत्मदाह के रूप में देखल जाला।<ref name="Nikhilananda p44—45" /> {{clear}} == संदर्भ == {{Reflist|29em}} == स्रोत == * {{cite book|last=Nikhilananda|first=Swami|title=Vivekânanda: a biography|url=http://www.vivekananda.net/PDFBooks/BiographybyNikhilananda.pdf|year=1953|publisher=Ramakrishna-Vivekananda Center|isbn=978-0-911206-25-8}} [[श्रेणी:भारतीय हिंदू संत]] [[श्रेणी:भारतीय हिंदू योगी]] {{Bio-stub}} 0kkah698wo47d6ozei5s83hynakg7tt 803390 803389 2026-07-29T18:11:49Z SM7 3953 ज्ञानसंदूक जोड़ल गइल 803390 wikitext text/x-wiki {{Infobox Hindu leader |religion = [[हिंदू धर्म]] |birth_date = 1798 |birth_place = [[जौनपुर जिला|जौनपुर]] |death_date = 1898 |death_place = [[गाजीपुर]] (उमिर 99-100) |death_cause = दाह (आत्म-दाह)<ref name="Nikhilananda p44—45" /> |philosophy = [[हठयोग]] |influenced = [[स्वामी विवेकानंद]] }} '''पौहारी बाबा''' (1798–1898), जिनका के '''पवहारी''' नाँव से भी जानल जाला, एगो हिंदू संन्यासी आ संत रहलें। उनकर जनम [[जौनपुर जिला|जौनपुर]] के प्रेमापुर गाँव में एगो ब्राह्मण परिवार में भइल रहे। बचपन में ऊ अपना चाचा के देखरेख में पढ़ाई करे खातिर [[गाजीपुर]] गइलें। उनकर चाचा [[रामानुज]] भा श्री संप्रदाय के अनुयायी रहलें। पढ़ाई पूरा कइला के बाद पौहारी बाबा कई गो जगह के यात्रा कइलें। [[काठियावाड़]] के [[गिरनार]] में उनकरा के [[योग]] के दीक्षा मिलल।<ref name="Nikhilananda p44—45">{{Harvnb|Nikhilananda|1953|pp=44–45}}</ref> ओकरा बाद ऊ गाजीपुर लवट अइलें आ अपना घरे में जमीन के नीचे एगो साधना-स्थल (गुफा जइसन आश्रम) बनवलें, जहाँ ऊ कई-कई दिन तक [[ध्यान]] आ योग के साधना करत रहलें। ऊ अपना नम्रता, विनम्र व्यवहार आ परोपकार के भावना खातिर जानल जात रहलें। कथा हवे कि, एक रात एगो चोर उनकर आश्रम में घुस गइल। जब चोर चोरी कइल सामान छोड़के भागे लागल, त नींद से जागला के बाद पौहारी बाबा ओकरा पाछे दौड़लें आ उल्टे ऊ सामान चोर के देवे के कोशिश कइलें। एह घटना के चोर पर एतना गहिर असर पड़ल कि बाद में ऊ संन्यासी बन गइल आ पौहारी बाबा के अनुयायी हो गइल। 1890 में [[स्वामी विवेकानंद]] गाजीपुर गइलें आ पौहारी बाबा से भेंट कइलें। [[सिस्टर निवेदिता]] के अनुसार, 1898 में पौहारी बाबा आग में जलके देह त्याग दिहलें। एह घटना के सामान्य रूप से आत्मदाह के रूप में देखल जाला।<ref name="Nikhilananda p44—45" /> {{clear}} == संदर्भ == {{Reflist|29em}} == स्रोत == * {{cite book|last=Nikhilananda|first=Swami|title=Vivekânanda: a biography|url=http://www.vivekananda.net/PDFBooks/BiographybyNikhilananda.pdf|year=1953|publisher=Ramakrishna-Vivekananda Center|isbn=978-0-911206-25-8}} [[श्रेणी:भारतीय हिंदू संत]] [[श्रेणी:भारतीय हिंदू योगी]] {{Bio-stub}} ofrlg1e4mjnomvlre1u0yvazcf8zq4t 803391 803390 2026-07-29T18:12:32Z SM7 3953 [[विकिपीडिया:हॉट-कैट|हॉट-कैट]] द्वारा [[श्रेणी:गाजीपुर के लोग]] जोड़ल गइल 803391 wikitext text/x-wiki {{Infobox Hindu leader |religion = [[हिंदू धर्म]] |birth_date = 1798 |birth_place = [[जौनपुर जिला|जौनपुर]] |death_date = 1898 |death_place = [[गाजीपुर]] (उमिर 99-100) |death_cause = दाह (आत्म-दाह)<ref name="Nikhilananda p44—45" /> |philosophy = [[हठयोग]] |influenced = [[स्वामी विवेकानंद]] }} '''पौहारी बाबा''' (1798–1898), जिनका के '''पवहारी''' नाँव से भी जानल जाला, एगो हिंदू संन्यासी आ संत रहलें। उनकर जनम [[जौनपुर जिला|जौनपुर]] के प्रेमापुर गाँव में एगो ब्राह्मण परिवार में भइल रहे। बचपन में ऊ अपना चाचा के देखरेख में पढ़ाई करे खातिर [[गाजीपुर]] गइलें। उनकर चाचा [[रामानुज]] भा श्री संप्रदाय के अनुयायी रहलें। पढ़ाई पूरा कइला के बाद पौहारी बाबा कई गो जगह के यात्रा कइलें। [[काठियावाड़]] के [[गिरनार]] में उनकरा के [[योग]] के दीक्षा मिलल।<ref name="Nikhilananda p44—45">{{Harvnb|Nikhilananda|1953|pp=44–45}}</ref> ओकरा बाद ऊ गाजीपुर लवट अइलें आ अपना घरे में जमीन के नीचे एगो साधना-स्थल (गुफा जइसन आश्रम) बनवलें, जहाँ ऊ कई-कई दिन तक [[ध्यान]] आ योग के साधना करत रहलें। ऊ अपना नम्रता, विनम्र व्यवहार आ परोपकार के भावना खातिर जानल जात रहलें। कथा हवे कि, एक रात एगो चोर उनकर आश्रम में घुस गइल। जब चोर चोरी कइल सामान छोड़के भागे लागल, त नींद से जागला के बाद पौहारी बाबा ओकरा पाछे दौड़लें आ उल्टे ऊ सामान चोर के देवे के कोशिश कइलें। एह घटना के चोर पर एतना गहिर असर पड़ल कि बाद में ऊ संन्यासी बन गइल आ पौहारी बाबा के अनुयायी हो गइल। 1890 में [[स्वामी विवेकानंद]] गाजीपुर गइलें आ पौहारी बाबा से भेंट कइलें। [[सिस्टर निवेदिता]] के अनुसार, 1898 में पौहारी बाबा आग में जलके देह त्याग दिहलें। एह घटना के सामान्य रूप से आत्मदाह के रूप में देखल जाला।<ref name="Nikhilananda p44—45" /> {{clear}} == संदर्भ == {{Reflist|29em}} == स्रोत == * {{cite book|last=Nikhilananda|first=Swami|title=Vivekânanda: a biography|url=http://www.vivekananda.net/PDFBooks/BiographybyNikhilananda.pdf|year=1953|publisher=Ramakrishna-Vivekananda Center|isbn=978-0-911206-25-8}} [[श्रेणी:भारतीय हिंदू संत]] [[श्रेणी:भारतीय हिंदू योगी]] [[श्रेणी:गाजीपुर के लोग]] {{Bio-stub}} rv3i79edg5arjtxtdvitwzgb59wrsvg दयानंद सरस्वती 0 101347 803393 2026-07-29T18:43:23Z SM7 3953 पन्ना बनावल गइल "'''दयानंद सरस्वती''' (12 फरवरी 1824 – 30 अक्टूबर 1883), जिनकर जनम मूलशंकर तिवारी के रूप में भइल रहे, एगो हिंदू दार्शनिक, समाज सुधारक आ [[आर्य समाज]] के संस्थापक रहलें। आर्य समाज हिंदू..." के साथ 803393 wikitext text/x-wiki '''दयानंद सरस्वती''' (12 फरवरी 1824 – 30 अक्टूबर 1883), जिनकर जनम मूलशंकर तिवारी के रूप में भइल रहे, एगो हिंदू दार्शनिक, समाज सुधारक आ [[आर्य समाज]] के संस्थापक रहलें। आर्य समाज हिंदू धर्म के एगो सुधारवादी आंदोलन ह। उनकर लिखल किताब [[सत्यार्थ प्रकाश]] [[वेद]] के दर्शन आ मनुष्य के अलग-अलग विचार आ कर्तव्य के व्याख्या करे वाला सबसे प्रभावशाली ग्रंथ सभ में से एगो मानल जाला। 1876 में दयानंद सरस्वती सबसे पहिले "भारत भारतीयन खातिर" के नारा देत [[स्वराज]] के आह्वान कइलें। बाद में एह नारा के [[लोकमान्य तिलक]] भी अपनवलें। मूर्ति-पूजा आ कर्मकांड के विरोध करत ऊ वैदिक धर्म के पुनर्जीवित करे के दिशा में काम कइलें। बाद में भारत के दार्शनिक आ राष्ट्रपति [[सर्वपल्ली राधाकृष्णन]] दयानंद सरस्वती के "आधुनिक भारत के निर्माता" सभ में से एगो कहलें। [[श्री अरविंद]] भी उनकरा बारे में एह तरह के सम्मानजनक विचार व्यक्त कइलें। {{clear}} {{जीवनी-आधार}} e39de7xd6zh9k9mxul3tokvorfos1yh 803395 803393 2026-07-29T18:48:10Z SM7 3953 [[विकिपीडिया:हॉट-कैट|हॉट-कैट]] द्वारा [[श्रेणी:भारतीय हिंदू धार्मिक नेता]] जोड़ल गइल 803395 wikitext text/x-wiki '''दयानंद सरस्वती''' (12 फरवरी 1824 – 30 अक्टूबर 1883), जिनकर जनम मूलशंकर तिवारी के रूप में भइल रहे, एगो हिंदू दार्शनिक, समाज सुधारक आ [[आर्य समाज]] के संस्थापक रहलें। आर्य समाज हिंदू धर्म के एगो सुधारवादी आंदोलन ह। उनकर लिखल किताब [[सत्यार्थ प्रकाश]] [[वेद]] के दर्शन आ मनुष्य के अलग-अलग विचार आ कर्तव्य के व्याख्या करे वाला सबसे प्रभावशाली ग्रंथ सभ में से एगो मानल जाला। 1876 में दयानंद सरस्वती सबसे पहिले "भारत भारतीयन खातिर" के नारा देत [[स्वराज]] के आह्वान कइलें। बाद में एह नारा के [[लोकमान्य तिलक]] भी अपनवलें। मूर्ति-पूजा आ कर्मकांड के विरोध करत ऊ वैदिक धर्म के पुनर्जीवित करे के दिशा में काम कइलें। बाद में भारत के दार्शनिक आ राष्ट्रपति [[सर्वपल्ली राधाकृष्णन]] दयानंद सरस्वती के "आधुनिक भारत के निर्माता" सभ में से एगो कहलें। [[श्री अरविंद]] भी उनकरा बारे में एह तरह के सम्मानजनक विचार व्यक्त कइलें। {{clear}} [[श्रेणी:भारतीय हिंदू धार्मिक नेता]] {{जीवनी-आधार}} ek3gy5kjrfmarq4njhyq0yf4fhm7zsg विमियो 0 101348 803397 2026-07-29T23:21:53Z SM7 3953 नया आधार लेख / अंग्रेजी विकिपीडिया से अनुबाद कइ के 803397 wikitext text/x-wiki '''विमियो''' एगो अमेरिकी [[ऑनलाइन बीडियो प्लेटफॉर्म|बीडियो होस्टिंग]], बीडियो साझा करे आ [[बीडियो]] से जुड़ल सेवा देवे वाला प्लेटफार्म ह। एकर स्थापना 2004 में भइल आ एकर मुख्यालय [[न्यूयॉर्क सिटी]] में बा। विमियो के खास ध्यान अलग-अलग तरह के डिवाइसन पर हाई-डेफिनिशन (ऊँच-क्वालिटी वाला) बीडियो उपलब्ध करावे पर बा। ई ''सॉफ्टवेयर ऐज़ अ सर्विस'' (SaaS) बिजनेस मॉडल पर काम करेला। एह प्लेटफॉर्म पर बीडियो बनावे, एडिट करे आ लाइव ब्रॉडकास्ट करे के औजार उपलब्ध बाड़ें। एकरा अलावा ई एंटरप्राइजन खातिर सॉफ्टवेयर सोल्यूशनो देला आ बीडियो प्रोफेशनल लोग के अपना ग्राहक आ दूसर पेशेवर लोग से जुड़े के सुविधा भी उपलब्ध करावेला। दिसंबर 2021 तक एह मंच पर लगभग 26 करोड़ उपयोगकर्ता रहलें, जवना में से करीब 16 लाख लोग एकर पेमेंट वाला सर्विस के सदस्य रहल। विमियो के शुरुआत 2004 में [[जेक लोडविक]] आ [[ज़ैक क्लेन]] कॉलेजह्यूमर के एगो एक्सपेरिमेंटल प्रोजेक्ट के रूप में कइलें। एह प्रोजेक्ट के मोटिवेशन ओही साल शुरू भइल फोटो साझा करे वाला मंच [[फ़्लिकर]] से मिलल रहे। बाद में एकरा के कॉलेजह्यूमर के मूल कंपनी कनेक्टेड वेंचर्स के एगो डिपार्टमेंट के रूप में संगठित कइल गइल। एह स्टार्टअप के स्थापना रिकी वैन वीन, जोश अब्रामसन, जेक लोडविक आ ज़ैक क्लेन मिलके कइल लोग। अगस्त 2006 में आईएसी (IAC) 2.1 करोड़ अमेरिकी डॉलर में कनेक्टेड वेंचर्स में 51 परसेंट कंट्रोलिंग हिस्सेदारी खरीद लिहलस। एकरा से तीनो महीना से कम समय बाद [[गूगल]], [[यूट्यूब]] के, 165 करोड़ अमेरिकी डॉलर से बेसी रकम में खरीद लिहलस। एह अधिग्रहण के बाद आईएसी विमियो के विकास पर अउरी बेसी ध्यान देवे लागल। 2008 तक जेक लोडविक आ ज़ैक क्लेन कंपनी छोड़ दिहलें, आ आईएसी विमियो के सेवा के विस्तार करे खातिर एकरा के अउरी पेशेवर कॉर्पोरेट ढाँचा दे दिहलस। मई 2021 में विमियो एगो स्वतंत्र पब्लिक कंपनी बन गइल। हालाँकि, सितंबर 2025 में बेंडिंग स्पून्स 138 करोड़ अमेरिकी डॉलर के पूरा नकद सौदा के तहत विमियो के निजी कंपनी बनावे के घोषणा कइलस। ई सौदा नवंबर 2025 में पूरा भइल। {{clear}} == संदर्भ == {{Reflist|29em}} [[श्रेणी:अमेरिकी कंपनी]] {{कंपनी-आधार}} c3v2c1jcn0gh3jru6y5zmxi045d4ha2 803458 803397 2026-07-29T23:50:00Z SM7 3953 सुधार कइल गइल 803458 wikitext text/x-wiki {{Infobox company | name = वीमियो, इंक. | trade_name = वीमियो | logo = Vimeo Logo.svg | founder = {{ubl|[[जेक लॉडविक]] ([[मुख्य प्रौद्योगिकी अधिकारी|सह-संस्थापक]])|[[जैक क्लेन]] ([[मुख्य डिजाइन अधिकारी|सह-संस्थापक]])|[[जोश अब्रामसन]] ([[मुख्य वित्तीय अधिकारी|भागीदार]])|[[रिकी वैन वीन]] ([[मुख्य परिचालन अधिकारी|भागीदार]])}} | type = [[सहायक कंपनी]] | traded_as = {{ubl|class=nowrap|{{NASDAQ was|VMEO}}}} (2021–2025) | founded = {{Launch date and age|2004|11|14}} | area_served = दुनिया भर में ([[चीन]], [[इंडोनेशिया]], [[उत्तर कोरिया]] आ [[तुर्की]] के छोड़के) | subsidiaries = {{ubl|[[वीएचएक्स]]|[[वीमियो लाइवस्ट्रीम]]|[[मैजिस्टो]]}} | hq_location_city = [[न्यूयॉर्क सिटी]] | hq_location_country = संयुक्त राज्य अमेरिका | key_people = {{ubl|फिलिप मोयर ([[मुख्य कार्यकारी अधिकारी|सीईओ]])}} | num_employees = 1,102 (दिसंबर 2024) | parent = {{ubl|[[आईएसी]] (2006–2021)|[[बेंडिंग स्पून्स]] (2025–अबले)|}} | products = {{ubl|[[वीएचएक्स|वीमियो ओटीटी]]|[[वीमियो लाइवस्ट्रीम]]|वीमियो स्टॉक|वीमियो एंटरप्राइज|वीमियो क्रिएट|वीमियो रिकॉर्ड}} | revenue = {{Unchanged}} {{US$|417 million|link=yes}} (2024) | operating_income = {{Increase}} {{US$|19 million}} (2024) | net_income = {{Increase}} {{US$|27 million}} (2024) | assets = {{Increase}} {{US$|643 million}} (2024) | equity = {{Increase}} {{US$|409 million}} (2024) | website = {{URL|https://vimeo.com}} | module = {{Infobox website | embed = yes | name = <wbr />&#8203;<strong>Vimeo.com</strong> | current_status = सक्रिय | num_users = 300 मिलियन (2023) | website_type = [[वीडियो होस्टिंग सेवा]], [[सॉफ्टवेयर एगो सेवा के रूप में]] | launch_date = {{start date and age|2004|12|15}} | advertising = ना | registration = वैकल्पिक | language = 7 भाषा: अंग्रेजी, स्पेनी, जर्मन, फ्रेंच, जापानी, पुर्तगाली, कोरियाई }} }} '''विमियो''' एगो अमेरिकी [[ऑनलाइन बीडियो प्लेटफॉर्म|बीडियो होस्टिंग]], बीडियो साझा करे आ [[बीडियो]] से जुड़ल सेवा देवे वाला प्लेटफार्म ह। एकर स्थापना 2004 में भइल आ एकर मुख्यालय [[न्यूयॉर्क सिटी]] में बा। विमियो के खास ध्यान अलग-अलग तरह के डिवाइसन पर हाई-डेफिनिशन (ऊँच-क्वालिटी वाला) बीडियो उपलब्ध करावे पर बा। ई ''सॉफ्टवेयर ऐज़ अ सर्विस'' (SaaS) बिजनेस मॉडल पर काम करेला। एह प्लेटफॉर्म पर बीडियो बनावे, एडिट करे आ लाइव ब्रॉडकास्ट करे के औजार उपलब्ध बाड़ें। एकरा अलावा ई एंटरप्राइजन खातिर सॉफ्टवेयर सोल्यूशनो देला आ बीडियो प्रोफेशनल लोग के अपना ग्राहक आ दूसर पेशेवर लोग से जुड़े के सुविधा भी उपलब्ध करावेला। दिसंबर 2021 तक एह मंच पर लगभग 26 करोड़ उपयोगकर्ता रहलें, जवना में से करीब 16 लाख लोग एकर पेमेंट वाला सर्विस के सदस्य रहल। विमियो के शुरुआत 2004 में [[जेक लोडविक]] आ [[ज़ैक क्लेन]] कॉलेजह्यूमर के एगो एक्सपेरिमेंटल प्रोजेक्ट के रूप में कइलें। एह प्रोजेक्ट के मोटिवेशन ओही साल शुरू भइल फोटो साझा करे वाला मंच [[फ़्लिकर]] से मिलल रहे। बाद में एकरा के कॉलेजह्यूमर के मूल कंपनी कनेक्टेड वेंचर्स के एगो डिपार्टमेंट के रूप में संगठित कइल गइल। एह स्टार्टअप के स्थापना रिकी वैन वीन, जोश अब्रामसन, जेक लोडविक आ ज़ैक क्लेन मिलके कइल लोग। अगस्त 2006 में आईएसी (IAC) 2.1 करोड़ अमेरिकी डॉलर में कनेक्टेड वेंचर्स में 51 परसेंट कंट्रोलिंग हिस्सेदारी खरीद लिहलस। एकरा से तीनो महीना से कम समय बाद [[गूगल]], [[यूट्यूब]] के, 165 करोड़ अमेरिकी डॉलर से बेसी रकम में खरीद लिहलस। एह अधिग्रहण के बाद आईएसी विमियो के विकास पर अउरी बेसी ध्यान देवे लागल। 2008 तक जेक लोडविक आ ज़ैक क्लेन कंपनी छोड़ दिहलें, आ आईएसी विमियो के सेवा के विस्तार करे खातिर एकरा के अउरी पेशेवर कॉर्पोरेट ढाँचा दे दिहलस। मई 2021 में विमियो एगो स्वतंत्र पब्लिक कंपनी बन गइल। हालाँकि, सितंबर 2025 में बेंडिंग स्पून्स 138 करोड़ अमेरिकी डॉलर के पूरा नकद सौदा के तहत विमियो के निजी कंपनी बनावे के घोषणा कइलस। ई सौदा नवंबर 2025 में पूरा भइल। {{clear}} == संदर्भ == {{Reflist|29em}} [[श्रेणी:अमेरिकी कंपनी]] {{कंपनी-आधार}} gr0906uhpulkwvia8t9esb3ps06xa89 803476 803458 2026-07-30T00:05:24Z SM7 3953 सुधार कइल गइल 803476 wikitext text/x-wiki <!-- {{Infobox company | name = वीमियो, इंक. | trade_name = वीमियो | logo = Vimeo Logo.svg | founder = {{ubl|[[जेक लॉडविक]] ([[मुख्य प्रौद्योगिकी अधिकारी|सह-संस्थापक]])|[[जैक क्लेन]] ([[मुख्य डिजाइन अधिकारी|सह-संस्थापक]])|[[जोश अब्रामसन]] ([[मुख्य वित्तीय अधिकारी|भागीदार]])|[[रिकी वैन वीन]] ([[मुख्य परिचालन अधिकारी|भागीदार]])}} | type = [[सहायक कंपनी]] | traded_as = {{ubl|class=nowrap|{{NASDAQ was|VMEO}}}} (2021–2025) | founded = {{Launch date and age|2004|11|14}} | area_served = दुनिया भर में ([[चीन]], [[इंडोनेशिया]], [[उत्तर कोरिया]] आ [[तुर्की]] के छोड़के) | subsidiaries = {{ubl|[[वीएचएक्स]]|[[वीमियो लाइवस्ट्रीम]]|[[मैजिस्टो]]}} | hq_location_city = [[न्यूयॉर्क सिटी]] | hq_location_country = संयुक्त राज्य अमेरिका | key_people = {{ubl|फिलिप मोयर ([[मुख्य कार्यकारी अधिकारी|सीईओ]])}} | num_employees = 1,102 (दिसंबर 2024) | parent = {{ubl|[[आईएसी]] (2006–2021)|[[बेंडिंग स्पून्स]] (2025–अबले)|}} | products = {{ubl|[[वीएचएक्स|वीमियो ओटीटी]]|[[वीमियो लाइवस्ट्रीम]]|वीमियो स्टॉक|वीमियो एंटरप्राइज|वीमियो क्रिएट|वीमियो रिकॉर्ड}} | revenue = {{Unchanged}} {{US$|417 million|link=yes}} (2024) | operating_income = {{Increase}} {{US$|19 million}} (2024) | net_income = {{Increase}} {{US$|27 million}} (2024) | assets = {{Increase}} {{US$|643 million}} (2024) | equity = {{Increase}} {{US$|409 million}} (2024) | website = {{URL|https://vimeo.com}} | module = {{Infobox website | embed = yes | name = <wbr />&#8203;<strong>Vimeo.com</strong> | current_status = सक्रिय | num_users = 300 मिलियन (2023) | website_type = [[वीडियो होस्टिंग सेवा]], [[सॉफ्टवेयर एगो सेवा के रूप में]] | launch_date = {{start date and age|2004|12|15}} | advertising = ना | registration = वैकल्पिक | language = 7 भाषा: अंग्रेजी, स्पेनी, जर्मन, फ्रेंच, जापानी, पुर्तगाली, कोरियाई }} | footnotes = <ref name=AR24>{{cite web |title=Vimeo, Inc. 2024 Annual Report (Form 10-K) |url=https://www.sec.gov/Archives/edgar/data/1837686/000183768625000024/vmeo-20241231.htm |website=SEC.gov |publisher=[[U.S. Securities and Exchange Commission]] |date=February 19, 2025 |access-date=March 13, 2025}}</ref><ref name=AR>{{cite web |title=Vimeo, Inc. 2023 Annual Report (Form 10-K) |url=https://www.sec.gov/ix?doc=/Archives/edgar/data/1837686/000183768624000021/vmeo-20231231.htm |website=SEC.gov |publisher=[[U.S. Securities and Exchange Commission]] |date=27 February 2023 |access-date=May 21, 2024 |archive-date=May 21, 2024 |archive-url=https://web.archive.org/web/20240521023149/https://www.sec.gov/ix?doc=/Archives/edgar/data/1837686/000183768624000021/vmeo-20231231.htm |url-status=live }}</ref><ref name=Prototype>{{cite web |title=Vimeo on the Internet Archive |url=http://www.blumpy.org/vimeo/|archive-url=https://web.archive.org/web/20041217132740/http://www.blumpy.org/vimeo/ |archive-date=December 17, 2004}}</ref><ref name=domain>{{cite web|title=Whois Record for <code>vimeo.com</code> |url=https://whois.domaintools.com/vimeo.com |publisher=DomainTools |access-date=May 1, 2025}}</ref> }}--> '''विमियो''' ({{Langx|en|vimeo}}; {{IPAc-en|ˈ|v|ɪ|m|i|oʊ|}} {{respell|VIM|ee|oh}})<ref>{{cite web|url=https://vimeo.com/79338405|title=How to pronounce Vimeo?|publisher=Vimeo|access-date=January 17, 2019|archive-date=May 30, 2021|archive-url=https://web.archive.org/web/20210530224517/https://vimeo.com/79338405|url-status=live}}</ref> एगो अमेरिकी [[ऑनलाइन बीडियो प्लेटफॉर्म|बीडियो होस्टिंग]], बीडियो साझा करे आ [[बीडियो]] से जुड़ल सेवा देवे वाला प्लेटफार्म ह। एकर स्थापना 2004 में भइल आ एकर मुख्यालय [[न्यूयॉर्क सिटी]] में बा। विमियो के खास ध्यान अलग-अलग तरह के डिवाइसन पर हाई-डेफिनिशन (ऊँच-क्वालिटी वाला) बीडियो उपलब्ध करावे पर बा। ई ''सॉफ्टवेयर ऐज़ अ सर्विस'' (SaaS) बिजनेस मॉडल पर काम करेला। एह प्लेटफॉर्म पर बीडियो बनावे, एडिट करे आ लाइव ब्रॉडकास्ट करे के औजार उपलब्ध बाड़ें। एकरा अलावा ई एंटरप्राइजन खातिर सॉफ्टवेयर सोल्यूशनो देला आ बीडियो प्रोफेशनल लोग के अपना ग्राहक आ दूसर पेशेवर लोग से जुड़े के सुविधा भी उपलब्ध करावेला। दिसंबर 2021 तक एह मंच पर लगभग 26 करोड़ उपयोगकर्ता रहलें, जवना में से करीब 16 लाख लोग एकर पेमेंट वाला सर्विस के सदस्य रहल। विमियो के शुरुआत 2004 में [[जेक लोडविक]] आ [[ज़ैक क्लेन]] कॉलेजह्यूमर के एगो एक्सपेरिमेंटल प्रोजेक्ट के रूप में कइलें। एह प्रोजेक्ट के मोटिवेशन ओही साल शुरू भइल फोटो साझा करे वाला मंच [[फ़्लिकर]] से मिलल रहे। बाद में एकरा के कॉलेजह्यूमर के मूल कंपनी कनेक्टेड वेंचर्स के एगो डिपार्टमेंट के रूप में संगठित कइल गइल। एह स्टार्टअप के स्थापना रिकी वैन वीन, जोश अब्रामसन, जेक लोडविक आ ज़ैक क्लेन मिलके कइल लोग। अगस्त 2006 में आईएसी (IAC) 2.1 करोड़ अमेरिकी डॉलर में कनेक्टेड वेंचर्स में 51 परसेंट कंट्रोलिंग हिस्सेदारी खरीद लिहलस। एकरा से तीनो महीना से कम समय बाद [[गूगल]], [[यूट्यूब]] के, 165 करोड़ अमेरिकी डॉलर से बेसी रकम में खरीद लिहलस। एह अधिग्रहण के बाद आईएसी विमियो के विकास पर अउरी बेसी ध्यान देवे लागल। 2008 तक जेक लोडविक आ ज़ैक क्लेन कंपनी छोड़ दिहलें, आ आईएसी विमियो के सेवा के विस्तार करे खातिर एकरा के अउरी पेशेवर कॉर्पोरेट ढाँचा दे दिहलस। मई 2021 में विमियो एगो स्वतंत्र पब्लिक कंपनी बन गइल। हालाँकि, सितंबर 2025 में बेंडिंग स्पून्स 138 करोड़ अमेरिकी डॉलर के पूरा नकद सौदा के तहत विमियो के निजी कंपनी बनावे के घोषणा कइलस। ई सौदा नवंबर 2025 में पूरा भइल। {{clear}} == संदर्भ == {{Reflist|29em}} [[श्रेणी:अमेरिकी कंपनी]] {{कंपनी-आधार}} h6it9hcqhqk05gk00nk8ygwl0rystqz 803477 803476 2026-07-30T00:05:46Z SM7 3953 [[विकिपीडिया:हॉट-कैट|हॉट-कैट]] द्वारा [[श्रेणी:बीडियो होस्टिंग]] जोड़ल गइल 803477 wikitext text/x-wiki <!-- {{Infobox company | name = वीमियो, इंक. | trade_name = वीमियो | logo = Vimeo Logo.svg | founder = {{ubl|[[जेक लॉडविक]] ([[मुख्य प्रौद्योगिकी अधिकारी|सह-संस्थापक]])|[[जैक क्लेन]] ([[मुख्य डिजाइन अधिकारी|सह-संस्थापक]])|[[जोश अब्रामसन]] ([[मुख्य वित्तीय अधिकारी|भागीदार]])|[[रिकी वैन वीन]] ([[मुख्य परिचालन अधिकारी|भागीदार]])}} | type = [[सहायक कंपनी]] | traded_as = {{ubl|class=nowrap|{{NASDAQ was|VMEO}}}} (2021–2025) | founded = {{Launch date and age|2004|11|14}} | area_served = दुनिया भर में ([[चीन]], [[इंडोनेशिया]], [[उत्तर कोरिया]] आ [[तुर्की]] के छोड़के) | subsidiaries = {{ubl|[[वीएचएक्स]]|[[वीमियो लाइवस्ट्रीम]]|[[मैजिस्टो]]}} | hq_location_city = [[न्यूयॉर्क सिटी]] | hq_location_country = संयुक्त राज्य अमेरिका | key_people = {{ubl|फिलिप मोयर ([[मुख्य कार्यकारी अधिकारी|सीईओ]])}} | num_employees = 1,102 (दिसंबर 2024) | parent = {{ubl|[[आईएसी]] (2006–2021)|[[बेंडिंग स्पून्स]] (2025–अबले)|}} | products = {{ubl|[[वीएचएक्स|वीमियो ओटीटी]]|[[वीमियो लाइवस्ट्रीम]]|वीमियो स्टॉक|वीमियो एंटरप्राइज|वीमियो क्रिएट|वीमियो रिकॉर्ड}} | revenue = {{Unchanged}} {{US$|417 million|link=yes}} (2024) | operating_income = {{Increase}} {{US$|19 million}} (2024) | net_income = {{Increase}} {{US$|27 million}} (2024) | assets = {{Increase}} {{US$|643 million}} (2024) | equity = {{Increase}} {{US$|409 million}} (2024) | website = {{URL|https://vimeo.com}} | module = {{Infobox website | embed = yes | name = <wbr />&#8203;<strong>Vimeo.com</strong> | current_status = सक्रिय | num_users = 300 मिलियन (2023) | website_type = [[वीडियो होस्टिंग सेवा]], [[सॉफ्टवेयर एगो सेवा के रूप में]] | launch_date = {{start date and age|2004|12|15}} | advertising = ना | registration = वैकल्पिक | language = 7 भाषा: अंग्रेजी, स्पेनी, जर्मन, फ्रेंच, जापानी, पुर्तगाली, कोरियाई }} | footnotes = <ref name=AR24>{{cite web |title=Vimeo, Inc. 2024 Annual Report (Form 10-K) |url=https://www.sec.gov/Archives/edgar/data/1837686/000183768625000024/vmeo-20241231.htm |website=SEC.gov |publisher=[[U.S. Securities and Exchange Commission]] |date=February 19, 2025 |access-date=March 13, 2025}}</ref><ref name=AR>{{cite web |title=Vimeo, Inc. 2023 Annual Report (Form 10-K) |url=https://www.sec.gov/ix?doc=/Archives/edgar/data/1837686/000183768624000021/vmeo-20231231.htm |website=SEC.gov |publisher=[[U.S. Securities and Exchange Commission]] |date=27 February 2023 |access-date=May 21, 2024 |archive-date=May 21, 2024 |archive-url=https://web.archive.org/web/20240521023149/https://www.sec.gov/ix?doc=/Archives/edgar/data/1837686/000183768624000021/vmeo-20231231.htm |url-status=live }}</ref><ref name=Prototype>{{cite web |title=Vimeo on the Internet Archive |url=http://www.blumpy.org/vimeo/|archive-url=https://web.archive.org/web/20041217132740/http://www.blumpy.org/vimeo/ |archive-date=December 17, 2004}}</ref><ref name=domain>{{cite web|title=Whois Record for <code>vimeo.com</code> |url=https://whois.domaintools.com/vimeo.com |publisher=DomainTools |access-date=May 1, 2025}}</ref> }}--> '''विमियो''' ({{Langx|en|vimeo}}; {{IPAc-en|ˈ|v|ɪ|m|i|oʊ|}} {{respell|VIM|ee|oh}})<ref>{{cite web|url=https://vimeo.com/79338405|title=How to pronounce Vimeo?|publisher=Vimeo|access-date=January 17, 2019|archive-date=May 30, 2021|archive-url=https://web.archive.org/web/20210530224517/https://vimeo.com/79338405|url-status=live}}</ref> एगो अमेरिकी [[ऑनलाइन बीडियो प्लेटफॉर्म|बीडियो होस्टिंग]], बीडियो साझा करे आ [[बीडियो]] से जुड़ल सेवा देवे वाला प्लेटफार्म ह। एकर स्थापना 2004 में भइल आ एकर मुख्यालय [[न्यूयॉर्क सिटी]] में बा। विमियो के खास ध्यान अलग-अलग तरह के डिवाइसन पर हाई-डेफिनिशन (ऊँच-क्वालिटी वाला) बीडियो उपलब्ध करावे पर बा। ई ''सॉफ्टवेयर ऐज़ अ सर्विस'' (SaaS) बिजनेस मॉडल पर काम करेला। एह प्लेटफॉर्म पर बीडियो बनावे, एडिट करे आ लाइव ब्रॉडकास्ट करे के औजार उपलब्ध बाड़ें। एकरा अलावा ई एंटरप्राइजन खातिर सॉफ्टवेयर सोल्यूशनो देला आ बीडियो प्रोफेशनल लोग के अपना ग्राहक आ दूसर पेशेवर लोग से जुड़े के सुविधा भी उपलब्ध करावेला। दिसंबर 2021 तक एह मंच पर लगभग 26 करोड़ उपयोगकर्ता रहलें, जवना में से करीब 16 लाख लोग एकर पेमेंट वाला सर्विस के सदस्य रहल। विमियो के शुरुआत 2004 में [[जेक लोडविक]] आ [[ज़ैक क्लेन]] कॉलेजह्यूमर के एगो एक्सपेरिमेंटल प्रोजेक्ट के रूप में कइलें। एह प्रोजेक्ट के मोटिवेशन ओही साल शुरू भइल फोटो साझा करे वाला मंच [[फ़्लिकर]] से मिलल रहे। बाद में एकरा के कॉलेजह्यूमर के मूल कंपनी कनेक्टेड वेंचर्स के एगो डिपार्टमेंट के रूप में संगठित कइल गइल। एह स्टार्टअप के स्थापना रिकी वैन वीन, जोश अब्रामसन, जेक लोडविक आ ज़ैक क्लेन मिलके कइल लोग। अगस्त 2006 में आईएसी (IAC) 2.1 करोड़ अमेरिकी डॉलर में कनेक्टेड वेंचर्स में 51 परसेंट कंट्रोलिंग हिस्सेदारी खरीद लिहलस। एकरा से तीनो महीना से कम समय बाद [[गूगल]], [[यूट्यूब]] के, 165 करोड़ अमेरिकी डॉलर से बेसी रकम में खरीद लिहलस। एह अधिग्रहण के बाद आईएसी विमियो के विकास पर अउरी बेसी ध्यान देवे लागल। 2008 तक जेक लोडविक आ ज़ैक क्लेन कंपनी छोड़ दिहलें, आ आईएसी विमियो के सेवा के विस्तार करे खातिर एकरा के अउरी पेशेवर कॉर्पोरेट ढाँचा दे दिहलस। मई 2021 में विमियो एगो स्वतंत्र पब्लिक कंपनी बन गइल। हालाँकि, सितंबर 2025 में बेंडिंग स्पून्स 138 करोड़ अमेरिकी डॉलर के पूरा नकद सौदा के तहत विमियो के निजी कंपनी बनावे के घोषणा कइलस। ई सौदा नवंबर 2025 में पूरा भइल। {{clear}} == संदर्भ == {{Reflist|29em}} [[श्रेणी:अमेरिकी कंपनी]] [[श्रेणी:बीडियो होस्टिंग]] {{कंपनी-आधार}} q36g9gxwlbmrop4lppgub0cxm4bid67 Module:I18n/doc 828 101349 803398 2024-11-20T01:00:27Z en>Awesome Aasim 0 803398 wikitext text/x-wiki {{Lua autodoc|file=I18n|autodoc=yes}} <noinclude> [[Category:Module documentation pages]] </noinclude> ifmao5vx2uxg6hwm20cvnusihf83hmg 803399 803398 2026-07-29T23:40:19Z SM7 3953 1 revision imported from [[:en:Module:I18n/doc]] 803398 wikitext text/x-wiki {{Lua autodoc|file=I18n|autodoc=yes}} <noinclude> [[Category:Module documentation pages]] </noinclude> ifmao5vx2uxg6hwm20cvnusihf83hmg टेम्पलेट:Lua autodoc 10 101350 803400 2024-09-26T02:22:47Z en>Awesome Aasim 0 [[WP:AES|←]]Created page with '<includeonly>{{#invoke:Docbunto|main}}</includeonly><noinclude> {{documentation}} </noinclude>' 803400 wikitext text/x-wiki <includeonly>{{#invoke:Docbunto|main}}</includeonly><noinclude> {{documentation}} </noinclude> io4wp1dpjy0cnp37az2fv47kkj2k6hm 803401 803400 2026-07-29T23:40:40Z SM7 3953 1 revision imported from [[:en:Template:Lua_autodoc]] 803400 wikitext text/x-wiki <includeonly>{{#invoke:Docbunto|main}}</includeonly><noinclude> {{documentation}} </noinclude> io4wp1dpjy0cnp37az2fv47kkj2k6hm Module:Docbunto 828 101351 803402 2026-06-25T18:24:51Z en>Awesome Aasim 0 803402 Scribunto text/plain --- Docbunto is an automatic documentation generator for Scribunto modules. -- The module is based on LuaDoc and LDoc. It produces documentation in -- the form of MediaWiki markup, using `@tag`-prefixed comments embedded -- in the source code of a Scribunto module. The taglet parser & doclet -- renderer Docbunto uses are also publicly exposed to other modules. -- -- Docbunto code items are introduced by a block comment (`--[[]]--`), an -- inline comment with three hyphens (`---`), or an inline `@tag` comment. -- The module can use static code analysis to infer variable names, item -- privacy (`local` keyword), tables (`{}` constructor) and functions -- (`function` keyword). MediaWiki and Markdown formatting is supported. -- -- Items are usually rendered in the order they are defined, if they are -- public items, or emulated classes extending the Lua primitives. There -- are many customisation options available to change Docbunto behaviour. -- -- @module docbunto -- @alias p -- @require Module:I18n -- @require Module:Lua_lexer -- @require Module:Unindent -- @require Module:Yesno -- @require Module:Arguments -- @require Module:Documentation -- @author [[wikia:dev:User:8nml|8nml]] (Fandom Dev Wiki) -- @author [[User:Awesome Aasim|Awesome Aasim]] (Wikimedia port) -- @attribution [https://github.com/stevedonovan @stevedonovan] ([https://github.com/stevedonovan/LDoc GitHub]) -- @release beta -- <nowiki> local p = {} -- Module dependencies. local title = mw.title.getCurrentTitle() local i18n = require('Module:I18n').loadMessages('Docbunto'):useContentLang() local references = mw.loadData('Module:Docbunto/references') local lexer = require('Module:Lua lexer') local unindent = require('Module:Unindent') local yesno = require('Module:Yesno') local doc = require('Module:Documentation') local fileTooBig local usingOptionsContent local hasTodoItems -- Module variables. local DEFAULT_TITLE = title.namespace == 828 and doc.getEnvironment({}).templateTitle.text or '' local DEFAULT_VARIABLE = DEFAULT_TITLE :gsub('^%u', mw.ustring.lower) :gsub('%s', '_'):gsub('%p', '.') local frame, gsub, match local syntaxhighlightPresent = mw.getCurrentFrame() :preprocess("<syntaxhighlight lang=\"lua\">-- Lua</syntaxhighlight>") ~= "<syntaxhighlight lang=\"lua\">-- Lua</syntaxhighlight>" -- Docbunto variables & tag tokens. local TAG_MULTI = 'M' local TAG_ID = 'ID' local TAG_SINGLE = 'S' local TAG_TYPE = 'T' local TAG_FLAG = 'N' local TAG_MULTI_LINE = 'ML' -- Docbunto processing patterns. local patterns = {} --- Configuration options. -- @table options -- @field[opt] {boolean} options.all Include local items in -- documentation. -- @field[opt] {boolean} options.autodoc Whether the module is being -- called to automatically provide missing documentation. -- @field[opt] {boolean} options.boilerplate Removal of -- boilerplate (license block comments). -- @field[opt] {string} options.caption Infobox image caption. -- @field[opt] {boolean} options.code Only document Docbunto code -- items - exclude article infobox and lede from -- rendered documentation. Permits article to be -- edited in VisualEditor. -- @field[opt] {boolean} options.colon Format tags with a `:` suffix -- and without the `@` prefix. This bypasses the "doctag -- soup" some authors complain of. -- @field[opt] {string} options.image Infobox image. -- @field[opt] {boolean} options.box Include infobox in output. -- @field[opt] {boolean} options.footer Append a horizontal line & -- "generated by Docbunto" to the bottom of the output. -- @field[opt] {boolean} options.noluaref Don't link to the [[mw: -- Extension:Scribunto/Lua reference manual|Lua reference manual]] for types. -- @field[opt] {boolean} options.card Include endmatter card in the -- output at the end of the documentation. -- @field[opt] {boolean} options.plain Disable Markdown formatting -- in documentation. -- @field[opt] {string} options.preface Preface text to insert -- between lede & item documentation, used to provide -- usage and code examples. -- @field[opt] {boolean} options.simple Limit documentation to -- descriptions only. Removes documentation of -- subitem tags such as `@param` and `@field` ([[#Item -- subtags|see list]]). -- @field[opt] {boolean} options.sort Sort documentation items in -- alphabetical order. -- @field[opt] {boolean} options.strip Remove table index in -- documentation. -- @field[opt] {boolean} options.ulist Indent subitems as `<ul>` -- lists (LDoc/JSDoc behaviour). -- @field[opt] {boolean} options.verbose Show the code -- description of a package function when documenting -- function modules. -- Docbunto private utilities. local utils = {} --- @{string.find} optimisation for @{string} functions. -- @function utils.strfind_wrap -- @param {function} strfunc String library function. -- @return {function} Function wrapped in @{string.find} check. -- @local function utils.strfind_wrap(func) return function(...) local arg = {...} if string.find(arg[1], arg[2]) then return func(...); end end end --- String concatenation utility. -- @function join -- @param {sequence<string>} tbl Table of string segments -- @return {function} A concatenated string. -- @local function utils.join(tbl) return table.concat(tbl, '') end --- Pattern configuration function. -- Resets patterns for each documentation build. -- @function utils.configure_patterns -- @param {options} options Configuration options. -- @local function utils.configure_patterns(options) -- Setup Unicode or ASCII character encoding (optimisation). gsub = utils.strfind_wrap( options.unicode and mw.ustring.gsub or string.gsub ) match = utils.strfind_wrap( options.unicode and mw.ustring.match or string.match ) patterns.DOCBUNTO_SUMMARY = options.iso639_th and '^[^ ]+' or options.unicode and '^[^.։。।෴۔።]+[.։。।෴۔።]?' or '^[^.]+%.?' patterns.DOCBUNTO_CONCAT = options.iso639_th and '' or ' ' patterns.DOCBUNTO_TYPE = '^{({*[^}]+}*)}%s*' patterns.DOCBUNTO_WIKITEXT = '^[{|!}:#*=]+[%s-}]+' -- Setup parsing tag patterns with colon mode support. patterns.DOCBUNTO_TAG = options.colon and '^%s*(%w+):' or '^%s*@(%w+)' patterns.DOCBUNTO_TAG_COMMENT = '^[-%s]*' .. patterns.DOCBUNTO_TAG:sub(2) patterns.DOCBUNTO_TAG_VALUE = patterns.DOCBUNTO_TAG .. '(.*)' patterns.DOCBUNTO_TAG_MOD_VALUE = patterns.DOCBUNTO_TAG .. '%[([^%]]*)%](.*)' patterns.DOCBUNTO_TAG_OPTION_VALUE = '(%[[^%]]*%])%s*(.*)' end --- Tag processor function. -- @function utils.process_tag -- @param {string} str Tag string to process. -- @return {table} Tag object. -- @local function utils.process_tag(str) local tag = {} if str:find(patterns.DOCBUNTO_TAG_MOD_VALUE) then tag.name, tag.modifiers, tag.value = str:match(patterns.DOCBUNTO_TAG_MOD_VALUE) local modifiers = {} for m in tag.modifiers:gmatch('[^%s,]+') do local i = m:find('=') if i then local key = mw.text.trim(m:sub(1, i - 1)) local val = mw.text.trim(m:sub(i + 1, #m)) modifiers[key] = val else modifiers[m] = true end end if modifiers.optchain then modifiers.opt = true modifiers.optchain = nil end tag.modifiers = modifiers else tag.name, tag.value = str:match(patterns.DOCBUNTO_TAG_VALUE) end tag.value = mw.text.trim(tag.value) if p.tags._type_alias[tag.name] then if p.tags._type_alias[tag.name] ~= 'variable' then tag.value = p.tags._type_alias[tag.name] .. ' ' .. tag.value tag.name = 'field' end if tag.value:match('^%S+') ~= '...' then tag.value = tag.value:gsub('^(%S+)', '{%1}') end end tag.name = p.tags._alias[tag.name] or tag.name if tag.name ~= 'usage' and tag.value:find(patterns.DOCBUNTO_TYPE) then tag.type = tag.value:match(patterns.DOCBUNTO_TYPE) if tag.type:find('^%?') then tag.type = tag.type:sub(2) .. '|nil' end tag.value = tag.value:gsub(patterns.DOCBUNTO_TYPE, '') end if (tag.name == 'param' or tag.name == 'field') and tag.value:find(patterns.DOCBUNTO_TAG_OPTION_VALUE) then tag.value = tag.value:gsub(patterns.DOCBUNTO_TAG_OPTION_VALUE, '%1 %2') tag.modifiers = tag.modifiers or {} tag.modifiers.opt = true end if p.tags[tag.name] == TAG_FLAG then tag.value = true end return tag end --- Module info extraction utility. -- @function utils.extract_info -- @param {table} documentation Package doclet info. -- @return {table} Information name-value map. -- @local function utils.extract_info(documentation) local info = {} for _, tag in ipairs(documentation.tags) do if p.tags._module_info[tag.name] then if info[tag.name] then if not info[tag.name]:find('^%* ') then info[tag.name] = '* ' .. info[tag.name] end info[tag.name] = info[tag.name] .. '\n* ' .. tag.value else info[tag.name] = tag.value end end end return info end --- Type extraction utility. -- @function utils.extract_types -- @param {table} item Item documentation data. -- @return {sequence<string>} Item types. -- @local function utils.extract_types(item) local item_types = {} for _, tag in ipairs(item.tags) do if p.tags[tag.name] == TAG_TYPE then item_types[1] = tag.name if tag.name == 'variable' then local implied_local = utils.process_tag('@local') table.insert(item.tags, mw.clone(implied_local)) item.tags['local'] = mw.clone(implied_local) end if p.tags._generic_tags[item_types[1]] and not p.tags._project_level[item_types[1]] and tag.type then item_types[#item_types + 1] = tag.type end break end end return item_types end --- Name extraction utility. -- @function utils.extract_name -- @param {table} item Item documentation data. -- @param {boolean} project Whether the item is project-level. -- @return {string} Item name. -- @local function utils.extract_name(item, opts) opts = opts or {} local item_name for _, tag in ipairs(item.tags) do if p.tags[tag.name] == TAG_TYPE then item_name = tag.value; break end end if item_name or not opts.project then return item_name end item_name = item.code:match('\nreturn%s+([%w_]+)') if item_name == 'p' and not item.tags['alias'] then local implied_alias = { name = 'alias', value = 'p' } item.tags['alias'] = implied_alias table.insert(item.tags, implied_alias) end item_name = (item_name and item_name ~= 'p') and item_name or item.filename :gsub('^' .. mw.site.namespaces[828].name .. ':', '') :gsub('^(%u)', mw.ustring.lower) :gsub('/', '.'):gsub(' ', '_') return item_name end --- Source code utility for item name detection. -- @function utils.deduce_name -- @param {string} tokens Stream tokens for first line. -- @param {string} index Stream token index. -- @param {table} opts Configuration options. -- @param[opt] {boolean} opts.lookahead Whether a variable name succeeds the index. -- @param[opt] {boolean} opts.lookbehind Whether a variable name precedes the index. -- @return {string} Item name. -- @local function utils.deduce_name(tokens, index, opts) local name = '' if opts.lookbehind then for i2 = index, 1, -1 do if tokens[i2].type ~= 'keyword' then name = tokens[i2].data .. name else break end end elseif opts.lookahead then for i2 = index, #tokens do if tokens[i2].type ~= 'keyword' and not tokens[i2].data:find('^%(') then name = name .. tokens[i2].data else break end end end return name end --- Parses Lua item names to extract a hierarchy. --- @function utils.parse_hierarchy -- @param {string} item_name Documentation item name. -- @return {sequence<string>} List of global keys to index the -- variable reference. -- @local function utils.parse_hierarchy(item_name) local hierarchy = {} for _, lexeme in ipairs(lexer(item_name)) do if lexeme.type == 'ident' or lexeme.type == 'string' or lexeme.type == 'number' then hierarchy[#hierarchy + 1] = lexeme.text end end return hierarchy end --- Code analysis utility. -- @function utils.code_heuristic -- @param {table} item Item documentation data. -- @local function utils.code_heuristic(item) local is_section = item.tags['private'] or item.tags['local'] or item.type == 'type' if item.name and item.type and is_section then return end local tokens = lexer(item.code:match('^[^\n]*'))[1] local t, i = tokens[1], 1 local item_name, item_type local lexemes = {} while t do if t.type ~= 'whitespace' then lexemes[#lexemes + 1] = tokens[i] end t, i = tokens[i + 1], i + 1 end t, i = lexemes[1], 1 while t do if t.data == '=' and not item.name then item_name = utils.deduce_name(lexemes, i - 1, { lookbehind = true }) end if t.data == 'function' and not item.name then item_type = 'function' if lexemes[i + 1] and lexemes[i + 1].data ~= '(' then item_name = utils.deduce_name(lexemes, i + 1, { lookahead = true }) end end if t.data == '{' or t.data == '{}' and not item.type then item_type = 'table' end if t.data == 'local' and not is_section then local implied_local = utils.process_tag('@local') table.insert(item.tags, implied_local) item.tags['local'] = mw.clone(implied_local) end t, i = lexemes[i + 1], i + 1 end item.name = item.name or item_name or '' item.type = item.type or item_type if #(item.types or {}) == 0 then item.types[1] = item.type end end --- Array hash map conversion utility. -- @function utils.hash_map -- @param {table} item Item documentation data array. -- @return {table} Item documentation data map. -- @local function utils.hash_map(array) local map = array for _, element in ipairs(array) do if map[element.name] and not map[element.name].name then table.insert(map[element.name], mw.clone(element)) elseif map[element.name] and map[element.name].name then map[element.name] = { map[element.name], mw.clone(element) } else map[element.name] = mw.clone(element) end end return map end --- Item export utility. -- @function utils.export_item -- @param {table} documentation Package documentation data. -- @param {string} item_reference Identifier name for item. -- @param {string} item_index Identifier name for item. -- @param {string} item_alias Export alias for item. -- @param {boolean} factory_item Whether the documentation item is a factory function. -- @local function utils.export_item(documentation, item_reference, item_index, item_alias, factory_item) for _, item in ipairs(documentation.items) do if item_reference == item.name then item.tags['local'] = nil item.tags['private'] = nil for index, tag in ipairs(item.tags) do if p.tags._privacy_tags[tag.name] then table.remove(item.tags, index) end end item.type, item.types[1] = 'member', 'member' local accessor if item_alias:find('^%[') then accessor = '' else local is_class = documentation.type == 'classmod' or factory_item if is_class and not item.tags['static'] then accessor = ':' else accessor = '.' end end if factory_item then item.alias = utils.join{ documentation.items[item_index].tags['factory'].value, accessor, item_alias } else item.alias = utils.join{ ((documentation.tags['alias'] or {}).value or documentation.name), accessor, item_alias } end item.hierarchy = utils.parse_hierarchy(item.alias) break end end end --- Subitem tag correction utility. -- @function utils.correct_subitem_tag -- @param {table} item Item documentation data. -- @local function utils.correct_subitem_tag(item) local field_tag = item.tags['field'] if item.type ~= 'function' or not field_tag then return end if field_tag.name then field_tag.name = 'param' else for _, tag_el in ipairs(field_tag) do tag_el.name = 'param' end end local param_tag = item.tags['param'] if param_tag and not param_tag.name then if field_tag.name then table.insert(param_tag, field_tag) else for _, tag_el in ipairs(field_tag) do table.insert(param_tag, tag_el) end end elseif param_tag and param_tag.name then if field_tag.name then param_tag = { param_tag, field_tag } else for i, tag_el in ipairs(field_tag) do if i == 1 then param_tag = { param_tag } end for _, tag_el in ipairs(field_tag) do table.insert(param_tag, tag_el) end end end else param_tag = field_tag end item.tags['field'] = nil end --- Item override tag utility. -- @function utils.override_item_tag -- @param {table} item Item documentation data. -- @param {string} name Tag name. -- @param[opt] {string} alias Target alias for tag. -- @local function utils.override_item_tag(item, name, alias) if item.tags[name] then item[alias or name] = item.tags[name].value end end --- Identity function. -- @function utils.identity -- @param x The variable by reference. -- @return The parameter `x` by reference. -- @local function utils.identity(x) return x end --- Argument processing -- @param {string} funcName name of function -- @return function to process arguments -- @local local function makeInvokeFunc(funcName) return function (f) local args = require("Module:Arguments").getArgs(f, { valueFunc = function (key, value) if type(value) == 'string' then value = value:match('^%s*(.-)%s*$') -- Remove whitespace. if key == 'heading' or value ~= '' then return value else return nil end else return value end end }) return p[funcName](args) end end -- Docbunto rendering logic. local frontend = {} --- Markdown header converter. -- @function urils.markdown_header -- @param {string} hash Leading hash. -- @param {string} text Header text. -- @return {string} MediaWiki header. -- @local function frontend.markdown_header(hash, text) local symbol = '=' return utils.join{ '\n', symbol:rep(#hash), ' ', text, ' ', symbol:rep(#hash), '\n' } end --- Item reference formatting. -- @function urils.item_reference -- @param {string} ref Item reference. -- @return {string} Internal MediaWiki link to article item. -- @local function frontend.item_reference(ref) local temp = mw.text.split(ref, '|') local item = temp[1] local text = temp[2] or temp[1] if references.items[item] then local interwiki = 'mw' item = interwiki .. references.items[item] else item = '#' .. item end return mw.text.tag{ name = 'code', content = utils.join{ '[[', item, '|', text, ']]' } } end --- Doclet type reference preprocessor. -- Formats types with links to the [[mw:Extension:Scribunto/Lua reference manual|Lua reference manual]]. -- @function frontend.type_reference -- @param {table} item Item documentation data. -- @param {options} options Configuration options. -- @local function frontend.type_reference(item, options) local interwiki = 'mw:' if not options.noluaref and item.value and item.value:match('^%S+') == mw.text.tag('code', nil, '...') then item.value = item.value:gsub('^(%S+)', mw.text.tag{ name = 'code', content = utils.join{ '[[', interwiki, 'Lua reference manual#varargs|...]]' } }) end -- Handle tag type references. item.types = item.types or item.type and { item.type } if not item.types then return end for index, item_type in ipairs(item.types) do item.types[index] = item.types[index]:gsub('[%w_. ]+', function(item_type) local data = references.types[item_type] local name = data and data.name or item_type if not name:match('%.') and not name:match('^%u') and data then name = i18n:msg('type-' .. name) end if data and not options.noluaref then return utils.join{ '[[', interwiki, data.link, '|', name , ']]' } elseif item_type:find(i18n:msg('line-no'):gsub('$1', '')) then return utils.join { '[[' , options.file , '#L-', item_type:sub(6), '|', name, ']]' } elseif not options.noluaref and not p.tags._generic_tags[item_type] then return utils.join { '[[#', item_type, '|', name, ']]' } end end) end item.type = table.concat(item.types, i18n:msg('separator-dot')) end --- Markdown preprocessor to MediaWiki format. -- @function frontend.markdown -- @param {string} str Unprocessed Markdown string. -- @return {string} MediaWiki-compatible markup with HTML formatting. -- @local function frontend.markdown(str) -- Bold & italic tags. str = str:gsub('%*%*%*([^\n*]+)%*%*%*', '<b><i>%1</i></b>') str = str:gsub('%*%*([^\n*]+)%*%*', '<b>%1</b>') str = str:gsub('%*([^\n*]+)%*', '<i>%1</i>') -- Self-closing header support. str = str:gsub('%f[^\n%z](#+) *([^\n#]+) *#+%s', frontend.markdown_header) -- External and internal links. str = str:gsub('%[([^\n%]]+)%]%(([^\n][^\n)]-)%)', '[%2 %1]') str = str:gsub('%{@link%s+([^}]+)%}', '[%1]') str = str:gsub('%@{([^\n}]+)}', frontend.item_reference) -- Programming & scientific notation. str = str:gsub('%f["`]`([^\n`]+)`%f[^"`]', '<code><nowiki>%1</nowiki></code>') str = str:gsub('%$%$([^\n$]+)%$%$', '<math display="inline">%1</math>') -- Strikethroughs. str = str:gsub('~~([^\n~]+)~~', '<del>%1</del>') -- HTML output. return str end --- Doclet function item preprocessor. -- Formats item name as a function call with top-level arguments. -- @function frontend.pretask_function_name -- @param {table} item Item documentation data. -- @param {options} options Configuration options. -- @local function frontend.pretask_function_name(item, options) local target = item.alias and 'alias' or 'name' item[target] = item[target] .. '(' if item.tags['param'] and item.tags['param'].value and not item.tags['param'].value:find('^[%w_]+[.[]') then if (item.tags['param'].modifiers or {})['opt'] then local optional = mw.html.create('span') optional:css('opacity', '0.65') optional:wikitext(item.tags['param'].value:match('^(%S+)')) item[target] = item[target] .. tostring(optional) else item[target] = item[target] .. item.tags['param'].value:match('^(%S*)') end elseif item.tags['param'] then for index, tag in ipairs(item.tags['param']) do if not tag.value:find('^[%w_]+[.[]') then if (tag.modifiers or {})['opt'] then local param = mw.html.create('span') param:css('opacity', '0.65') param:wikitext((index > 1 and ', ' or '') .. tag.value:match('^%S+')) item[target] = item[target] .. tostring(param) else item[target] = item[target] .. (index > 1 and ', ' or '') .. tag.value:match('^(%S+)') end end end end item[target] = item[target] .. ')' end --- Doclet parameter/field subitem preprocessor. -- Indents and wraps variable prefix with `code` tag. -- @function pretask_variable_prefix -- @param {table} item Item documentation data. -- @param {options} options Configuration options. -- @local function frontend.pretask_variable_prefix(item, options) local indent_symbol = options.ulist and '*' or ':' local indent_level, indentation, item_id if item.value then indent_level = item.value:match('^%S*') == '...' and 0 or select(2, item.value:match('^%S*'):gsub('[.[]', '')) indentation = indent_symbol:rep(indent_level) item_id = options.item_id item.value = indentation .. item.value:gsub( '^(%S+)', mw.text.tag{ name = 'code', attrs = { id = item_id .. '~%1' }, content = '%1' }, 1 ) elseif item then for _, item_el in ipairs(item) do frontend.pretask_variable_prefix(item_el, options) end end end --- Doclet usage subitem preprocessor. -- Formats usage example with `<syntaxhighlight>` tag. -- @function frontend.pretask_usage_highlight -- @param {table} item Item documentation data. -- @param {options} options Configuration options. -- @local function frontend.pretask_usage_highlight(item, options) if item.value then item.value = unindent(mw.text.trim(item.value)) if item.value:find('^{{.+}}$') then item.value = item.value:gsub('=', mw.text.nowiki) local multi_line = item.value:find('\n') and '|m = 1|' or '|' if item.value:match('^{{([^:]+)') == '#invoke' then item.value = item.value:gsub('^{{#invoke:', '{{mlx' .. multi_line) else if options.entrypoint then item.value = item.value:gsub('^([^|]+)|%s*([^|}]-)(%s*)([|}])','%1|"%2"%3%4') end item.value = item.value:gsub('^{{', '{{tlx' .. multi_line) end local highlight_class = tonumber(mw.site.currentVersion:match('^%d%.%d+')) > 1.19 and 'mw-highlight' or 'mw-geshi' if item.value:find('\n') then local highlight_div = mw.html.create('div') highlight_div.addClass(highlight_class) highlight_div.addClass('mw-content-ltr') highlight_div:attr('dir', 'ltr') highlight_div:wikitext(item.value) item.value = tostring(highlight_div) else item.value = mw.text.tag{ name = 'span', attrs = { class = 'code' }, content = item.value } end else local highlight_tag = mw.html.create('syntaxhighlight') highlight_tag:attr('lang', 'lua') if not item.value:find('\n') then highlight_tag:attr('inline', 'inline') end highlight_tag:wikitext(item.value) item.value = tostring(highlight_tag) end elseif item then for _, item_el in ipairs(item) do frontend.pretask_usage_highlight(item_el, options) end end end --- Doclet error subitem preprocessor. -- Formats line numbers (`{#}`) in error tag values. -- @function frontend.pretask_error_line -- @param {table} item Item documentation data. -- @local function frontend.pretask_error_line(item, options) if item.name then local line if item.modifiers and item.modifiers.line then line = item.modifiers.line end for mod in pairs(item.modifiers or {}) do if mod:find('^%d+$') then line = mod end end if line then if item.types then item.types[#item.types + 1] = i18n:msg('line-no', line) else item.type = i18n:msg('line-no', line) end end elseif item then for _, item_el in ipairs(item) do frontend.pretask_error_line(item_el, options) end end end --- Doclet item renderer. -- @function frontend.render_item -- @param {table} stream Wikitext documentation stream. -- @param {table} item Item documentation data. -- @param {options} options Configuration options. -- @param[opt] {function} pretask Item data preprocessor. -- @local function frontend.render_item(stream, item, options, pretask) if pretask then pretask(item, options) end local item_name = item.alias or item.name frontend.type_reference(item, options) local item_type = item.type for _, name in ipairs(p.tags._subtype_hierarchy) do if item.tags[name] then item_type = item_type .. i18n:msg('separator-dot') .. name end end item_type = i18n:msg('parentheses', item_type) if options.strip and item.export and item.hierarchy then item_name = item_name:gsub('^[%w_]+[.[]?', '') end -- add single spaces between parentheses similar to -- MediaWiki documentation if not item_name:find( '()', 1, true ) then item_name = item_name:gsub('%(', '( ') item_name = item_name:gsub('%)', ' )') end item_name = item_name:gsub('%[', '[ ') item_name = item_name:gsub('%]', ' ]') local code_node = mw.html.create('code') :attr('id', options.item_id) :wikitext(item_name) if not usingOptionsContent then local span_node = stream:wikitext(';') :tag('span') :addClass('plainlinks docbunto-link') if fileTooBig or not syntaxhighlightPresent then span_node:wikitext('[{{fullurl:' .. options.file .. '|action=edit#mw-ce-l' .. item.lineno .. '}} ' .. tostring(code_node) .. ']') elseif title.namespace == 828 and options.file == title.text then span_node:wikitext('[[' .. '#L-' .. item.lineno .. '|' .. tostring(code_node) .. ']]') else span_node:wikitext('[[' .. options.file .. '#L-' .. item.lineno .. '|' .. tostring(code_node) .. ']]') end else stream:wikitext(';' .. tostring(code_node)) end stream :wikitext(item_type) :newline() if (#(item.summary or '') + #item.description) ~= 0 then local separator = #(item.summary or '') ~= 0 and #item.description ~= 0 and (item.description:find(patterns.DOCBUNTO_WIKITEXT) and '\n' or patterns.DOCBUNTO_CONCAT) or '' local intro = (item.summary or '') .. separator .. item.description intro = intro :gsub('\n([{:#*])', '\n:%1') :gsub('\n\n([^=])', '\n:%1') stream:wikitext(':' .. intro):newline() end end --- Doclet tag renderer. -- @function frontend.render_tag -- @param {table} stream Wikitext documentation stream. -- @param {string} name Item tag name. -- @param {table} tag Item tag data. -- @param {options} options Configuration options. -- @param[opt] {function} pretask Item data preprocessor. -- @local function frontend.render_tag(stream, name, tag, options, pretask) if pretask then pretask(tag, options) end if tag.value then frontend.type_reference(tag, options) local tag_name = i18n:msg('tag-' .. name, '1') -- Handle ul/ol/dl comment support. stream:wikitext(':') :tag('b') :wikitext(tag_name) :done():done() :wikitext(i18n:msg('separator-semicolon')) :wikitext((mw.text.trim(tag.value):gsub('\n([{:#*])', '\n:%1'))) if tag.value:find('\n[{:#*]') and (tag.type or (tag.modifiers or {})['opt']) then stream:newline():wikitext(':') end -- Type and optional appositive for tag. if tag.type and (tag.modifiers or {})['opt'] then stream:wikitext(i18n:msg{ key = 'parentheses', args = { tag.type .. i18n:msg('separator-colon') .. i18n:msg('optional') } }) elseif tag.type then stream:wikitext(i18n:msg{ key = 'parentheses', args = { tag.type } }) elseif (tag.modifiers or {})['opt'] then stream:wikitext(i18n:msg{ key = 'parentheses', args = { i18n:msg('optional') } }) end stream:newline() else local tag_name = i18n:msg('tag-' .. name, tostring(#tag)) stream :wikitext(':') :tag('b') :wikitext(tag_name) :done():done() :wikitext(i18n:msg('separator-semicolon')) :newline() for _, tag_el in ipairs(tag) do frontend.type_reference(tag_el, options) local marker = options.ulist and '*' or ':' stream:wikitext(':' .. marker .. tag_el.value:gsub('\n([{:#*])', '\n:' .. marker .. '%1')) if tag_el.value:find('\n[{:#*]') and (tag_el.type or (tag_el.modifiers or {})['opt']) then stream:newline():wikitext(':' .. marker .. (tag_el.value:match('^[*:]+') or '')) end if tag_el.type and (tag_el.modifiers or {})['opt'] then stream:wikitext(i18n:msg{ key = 'parentheses', args = { tag_el.type .. i18n:msg('separator-colon') .. i18n:msg('optional') } }) elseif tag_el.type then stream:wikitext(i18n:msg{ key = 'parentheses', args = { tag_el.type } }) elseif (tag_el.modifiers or {})['opt'] then stream:wikitext(i18n:msg{ key = 'parentheses', args = { i18n:msg('optional') } }) end stream:newline() end end end --- Template entrypoint for [[Template:Docbunto]]. -- @function p._main -- @param {table} args Arguments -- @return {string} Module documentation output. function p._main(args) frame = mw.getCurrentFrame() local modname = mw.text.trim(args[1] or args.file or DEFAULT_TITLE) local options = {} options.file = modname options.all = yesno(args.all or false) options.autodoc = yesno(args.autodoc or false) options.boilerplate = yesno(args.boilerplate or options.autodoc or false) options.box = yesno(args.box or args.infobox or options.autodoc or false) options.caption = args.caption options.card = yesno(args.card or (title.namespace == 828 and not options.autodoc)) options.code = yesno(args.code or false) options.colon = yesno(args.colon or false) options.footer = yesno(args.footer or title.namespace == 828) options.image = args.image options.migration = yesno(args.migration or false) options.noluaref = yesno(args.noluaref or false) options.plain = yesno(args.plain or false) options.preface = args.preface options.simple = yesno(args.simple or false) options.sort = yesno(args.sort or false) options.strip = yesno(args.strip or false) options.verbose = yesno(args.verbose or false) options.ulist = yesno(args.ulist or false) return p.build(modname, options) end --- Entrypoint for the module. -- @function p.main -- @param {Frame} frame Module frame. -- @return {string} Module documentation output. p.main = makeInvokeFunc('_main') --- Scribunto documentation generator entrypoint. -- @function p.build -- @param[opt] {string} modname Module page name (without namespace). -- Default: second-level subpage. -- @param[opt] {options} options Configuration options. function p.build(modname, options) modname = modname or DEFAULT_TITLE options = options or {} local tagdata = p.taglet(modname, options) local docdata = p.doclet(tagdata, options) return docdata end --- Docbunto taglet parser for Scribunto modules. -- @function p.taglet -- @param[opt] {string} modname Module page name (without namespace). -- @param[opt] {options} options Configuration options. -- @error[line=1081] {string} 'Lua source code not found in $1' -- @error[line=1087] {string} 'documentation markup for Docbunto not found in $1' -- @return {table} Module documentation data. function p.taglet(modname, options) modname = modname or DEFAULT_TITLE options = options or {} local filepath = mw.site.namespaces[828].name .. ':' .. modname local content = mw.title.new(filepath):getContent() -- Content checks. if options.content then content = options.content usingOptionsContent = true else content = mw.title.new(filepath):getContent() or error(i18n:msg('no-content', filepath), 0) end -- Syntax highlighting is disabled for files above a specific size fileTooBig = content:len() > 102400 if not content:match('%-%-%-') and not content:match(options.colon and '%w+:' or '@%w+') then error(i18n:msg('no-markup', filepath)) end -- Remove leading escapes. content = content:gsub('^%-%-+%s*<[^>]+>\n', '') -- Remove closing pretty comments. content = content:gsub('\n%-%-%-%-%-+(\n[^-]+)', '\n-- %1') -- Remove boilerplate block comments. if options.boilerplate then content = content:gsub('^%-%-%[=*%[\n.-\n%-?%-?%]%=*]%-?%-?%s+', '') content = content:gsub('%s+%-%-%[=*%[\n.-\n%-?%-?%]%=*]%-?%-?$', '') end -- Configure patterns for colon mode and Unicode character encoding. options.unicode = type(content:find('[^%w%c%p%s]+')) == 'number' options.iso639_th = type(content:find('\224\184[\129-\155]')) == 'number' utils.configure_patterns(options) -- Content lexing. local lines = lexer(content) local tokens = {} local dummy_token = { data = '', posFirst = 1, posLast = 1 } local token_closure = 0 for _, line in ipairs(lines) do if #line == 0 then dummy_token.type = token_closure == 0 and 'whitespace' or tokens[#tokens].type table.insert(tokens, mw.clone(dummy_token)) else for _, token in ipairs(line) do if token.data:find('^%[=*%[$') or token.data:find('^%-%-%[=*%[$') then token_closure = 1 end if token.data:find(']=*]') then token_closure = 0 end table.insert(tokens, token) end end end -- Start documentation data. local documentation = {} documentation.filename = filepath documentation.description = '' documentation.code = content documentation.comments = {} documentation.tags = {} documentation.items = {} local line_no = 0 local item_index = 0 -- Taglet tracking variables. local start_mode = true local comment_mode = false local doctag_mode = false local export_mode = false local special_tag = false local pragma_mode = false local factory_mode = false local return_mode = false local comment_tail = '' local tag_name = '' local new_item = false local new_tag = false local new_tag_item = false local new_item_code = false local pretty_comment = false local comment_brace = false local t, i, item = tokens[1], 1 local p_success, p_error = pcall(function() while t do -- Taglet variable update. new_item = t.data:find('^%-%-%-') or t.data:find('^%-%-%[%=*%[$') comment_tail = t.data:gsub('^%-%-+', '') tag_name = comment_tail:match(patterns.DOCBUNTO_TAG) tag_name = p.tags._alias[tag_name] or tag_name new_tag = type(p.tags[tag_name]) == 'string' local last_token = t.posFirst ~= 1 and 2 or 1 new_tag_item = ( new_tag and i > 1 and tokens[i - last_token].type ~= 'comment' and not tokens[i - last_token].data:find(patterns.DOCBUNTO_TAG_COMMENT) ) pretty_comment = t.data:find('^%-%-%-+%s*$') or t.data:find('[^-]+%-%-%-+%s*$') or t.data:find('%-*[ \t]*<nowiki>$') or t.data:find('%-*[ \t]*<pre>$') comment_brace = t.data:find('^%-%-%[=*%[$') or t.data:find('^%-%-%]=*%]$') or t.data:find('^%]=*%]%-%-$') pragma_mode = tag_name == 'pragma' export_mode = tag_name == 'export' and not doctag_mode special_tag = pragma_mode or export_mode local tags, tag, subtokens, separator -- Line counter. if t.posFirst == 1 then line_no = line_no + 1 end -- Data insertion logic. if t.type == 'comment' then if new_item then comment_mode = true end if (new_tag or new_tag_item) and not special_tag then comment_mode = true doctag_mode = true end -- Module-level documentation taglet. if start_mode then table.insert(documentation.comments, t.data) end -- Module description. if start_mode and comment_mode and not new_tag and not doctag_mode and not pretty_comment and not comment_brace then separator = mw.text.trim(comment_tail):find(patterns.DOCBUNTO_WIKITEXT) and '\n' or (#documentation.description ~= 0 and patterns.DOCBUNTO_CONCAT or '') documentation.description = utils.join{ documentation.description, separator, mw.text.trim(comment_tail) } end -- Switch from module description to module-level tag list. if start_mode and new_tag and not export_mode then doctag_mode = true table.insert(documentation.tags, utils.process_tag(comment_tail)) -- Concatenating module-level tag with more information. elseif start_mode and doctag_mode and not pretty_comment and not comment_brace then tags = documentation.tags if p.tags[tags[#tags].name] == TAG_MULTI then separator = mw.text.trim(comment_tail):find(patterns.DOCBUNTO_WIKITEXT) and '\n' or patterns.DOCBUNTO_CONCAT tags[#tags].value = utils.join{ tags[#tags].value, separator, mw.text.trim(comment_tail) } elseif p.tags[tags[#tags].name] == TAG_MULTI_LINE then tags[#tags].value = utils.join{ tags[#tags].value, '\n', comment_tail } end end -- Detect a new documentation item. if not start_mode and (new_item or new_tag_item) and not special_tag then table.insert(documentation.items, {}) item_index = item_index + 1 item = documentation.items[item_index] item.lineno = line_no item.code = '' item.comments = {} item.description = '' item.tags = {} end -- Concatenate an item with more information. if not start_mode and comment_mode and not new_tag and not doctag_mode and not pretty_comment and not comment_brace then separator = (mw.text.trim(comment_tail):find(patterns.DOCBUNTO_WIKITEXT)) and '\n' or (#item.description ~= 0 and patterns.DOCBUNTO_CONCAT or '') item.description = utils.join{ item.description, separator, mw.text.trim(comment_tail) } end -- Switch from item information to item tags. if not start_mode and new_tag and not special_tag then doctag_mode = true table.insert(item.tags, utils.process_tag(comment_tail)) -- Extend item tags with lines of further information. elseif not start_mode and doctag_mode and not pretty_comment and not comment_brace then tags = item.tags if p.tags[tags[#tags].name] == TAG_MULTI then separator = mw.text.trim(comment_tail):find(patterns.DOCBUNTO_WIKITEXT) and '\n' or patterns.DOCBUNTO_CONCAT tags[#tags].value = utils.join{ tags[#tags].value, separator, mw.text.trim(comment_tail) } elseif p.tags[tags[#tags].name] == TAG_MULTI_LINE then tags[#tags].value = utils.join{ tags[#tags].value, '\n', comment_tail } end end -- Save pre-item comments to item information. if not start_mode and (comment_mode or doctag_mode) then table.insert(item.comments, t.data) end -- Export tag support. if export_mode then factory_mode = t.posFirst ~= 1 if factory_mode then item.exports = true else documentation.exports = true end -- Parse export token data while concatenating code. subtokens = {} while t and (not factory_mode or (factory_mode and t.data ~= 'end')) do if factory_mode then item.code = utils.join{ item.code, (t.posFirst == 1 and '\n' or ''), t.data } end -- Fetch our item export tokens aka "subtokens". t, i = tokens[i + 1], i + 1 if t and t.posFirst == 1 then line_no = line_no + 1 end if t and t.type ~= 'whitespace' and t.type ~= 'keyword' and t.type ~= 'comment' then table.insert(subtokens, t) end end -- Tracking and boolean variables for parsing exports. local separator = { [','] = true, [';'] = true } local brace = { ['{'] = true, ['}'] = true } local item_reference, item_alias = '', '' local sequence_index, has_key = 0, false local subtoken, index = subtokens[2], 2 -- Logic that exports items to an item or module. while not brace[subtoken.data] do if subtoken.data == '=' then has_key = true elseif not separator[subtoken.data] then if has_key then item_reference = item_reference .. subtoken.data else item_alias = item_alias .. subtoken.data end end if subtokens[index + 1] and separator[subtokens[index + 1].data] or brace[subtokens[index + 1].data] then if not has_key then sequence_index = sequence_index + 1 item_reference, item_alias = item_alias, item_reference item_alias = '[' .. tostring(sequence_index) .. ']' end utils.export_item(documentation, item_reference, item_index, item_alias, factory_mode) item_reference, item_alias, has_key = '', '', false end subtoken, index = subtokens[index + 1], index + 1 end if not factory_mode then break -- end of file else factory_mode = false -- end of item end end -- Pragma tag support. if pragma_mode then tag = utils.process_tag(comment_tail) if tag.modifiers then options[tag.value] = yesno((next((tag ).modifiers or {})), true) elseif tag.value:find(' ') then local index = tag.value:find(' ') local key = tag.value:sub(1, index - 1) local val = tag.value:sub(index + 1) options[key] = yesno(val) elseif options[tag.value] == nil then options[tag.value] = true end end end -- Package data post-processing. if (t.type ~= 'comment' or t.data:find('^%-%-%]=*%]$') or t.data:find('^%]=*%]%-%-$')) and (comment_mode or doctag_mode) and start_mode then documentation.tags = utils.hash_map(documentation.tags) documentation.name = utils.extract_name(documentation, { project = true }) documentation.info = utils.extract_info(documentation) documentation.types = utils.extract_types(documentation) or { 'module' } documentation.type = documentation.types[1] -- N.B: summary field = 1st comment sentence; description = text remainder. if #documentation.description ~= 0 then documentation.summary = match(documentation.description, patterns.DOCBUNTO_SUMMARY) documentation.description = documentation.description:sub(select(2, documentation.description:find('%s*', #documentation.summary + 1)) + 1) end documentation.description = documentation.description:gsub('%s%s+', '\n\n') documentation.executable = p.tags._code_types[documentation.type] and true or false utils.correct_subitem_tag(documentation) utils.override_item_tag(documentation, 'name') utils.override_item_tag(documentation, 'alias') utils.override_item_tag(documentation, 'summary') utils.override_item_tag(documentation, 'description') utils.override_item_tag(documentation, 'class', 'type') end -- Item data post-processing. if (t.type ~= 'comment' or t.data:find('^%-%-%]=*%]$') or t.data:find('^%]=*%]%-%-$')) and (comment_mode or doctag_mode) and item_index ~= 0 then item.tags = utils.hash_map(item.tags) item.name = utils.extract_name(item) item.types = utils.extract_types(item) item.type = item.types[1] if #item.description ~= 0 then item.summary = match(item.description, patterns.DOCBUNTO_SUMMARY) item.description = item.description:sub(select(2, item.description:find('%s*', #item.summary + 1)) + 1) end item.description = item.description:gsub('%s%s+', '\n\n') new_item_code = true end -- Documentation block reset. if t.type ~= 'comment' or t.data:find('^%-%-%]=*%]$') or t.data:find('^%]=*%]%-%-$') then start_mode = false comment_mode = false doctag_mode = false export_mode = false pragma_mode = false end -- Don't concatenate module return value into item code. if t.data == 'return' and t.posFirst == 1 then return_mode = true end -- Item code concatenation. if item_index ~= 0 and not doctag_mode and not comment_mode and not comment_brace and not return_mode then separator = #item.code ~= 0 and t.posFirst == 1 and '\n' or '' item.code = utils.join{ item.code, separator, t.data } -- Code analysis on item headline. if new_item_code and item.code:find('\n') and t.posFirst == 1 then utils.code_heuristic(item) new_item_code = false end end t, i = tokens[i + 1], i + 1 end documentation.lineno = line_no local package_name = (documentation.tags['alias'] or {}).value or documentation.name or DEFAULT_VARIABLE local package_alias = (documentation.tags['alias'] or {}).value or 'p' local export_ptn = '^%s([.[])' for _, item in ipairs(documentation.items) do if item.name == package_alias or (item.name and item.name:match('^' .. package_alias .. '[.[]')) then item.alias = item.name:gsub(export_ptn:format(package_alias), documentation.name .. '%1') end if item.name == package_name or item.tags['export'] or (item.name and package_name and item.name:find(export_ptn:format(package_name))) or (item.alias and package_name and item.alias:find(export_ptn:format(package_name))) then item.export = true item.tags['local'] = nil item.tags['private'] = nil for index, tag in ipairs(item.tags) do if tag.name == 'local' or tag.name == 'private' then table.remove(item.tags, index) end end end if item.name and (item.name:find('[.:]') or item.name:find('%[[\'"]')) then item.hierarchy = utils.parse_hierarchy(item.name) end item.type = item.type or ((item.alias or item.name or ''):find('[.[]') and 'member' or 'variable') utils.correct_subitem_tag(item) utils.override_item_tag(item, 'name') utils.override_item_tag(item, 'alias') utils.override_item_tag(item, 'summary') utils.override_item_tag(item, 'description') utils.override_item_tag(item, 'class', 'type') end -- Sort documentation items by their access level. -- Items are sorted by whether they are exported at the module level. -- Items are then subsorted by whether the item is `@local` aka `@private`. table.sort(documentation.items, function(item1, item2) local inaccessible1 = item1.tags['local'] or item1.tags['private'] local inaccessible2 = item2.tags['local'] or item2.tags['private'] -- Send package items to the top. if item1.export and not item2.export then return true elseif item2.export and not item1.export then return false -- Send private items to the bottom. elseif inaccessible1 and not inaccessible2 then return false elseif inaccessible2 and not inaccessible1 then return true -- Optional alphabetical sort. elseif options.sort then return (item1.alias or item1.name) < (item2.alias or item2.name) -- Sort via source code order by default. else return item1.lineno < item2.lineno end end) end) if not p_success then mw.addWarning("Docbunto error: " .. p_error) end return documentation end --- Doclet renderer for Docbunto taglet data. -- @function p.doclet -- @param {table} data Taglet documentation data. -- @param[opt] {options} options Configuration options. -- @return {string} Wikitext documentation output. function p.doclet(data, options) local documentation = mw.html.create() local namespace_ptn = utils.join{ '^', mw.site.namespaces[828].name, ':' } local codepage = data.filename:gsub(namespace_ptn, '', 1) options = options or {} options.file = data.filename frame = frame or mw.getCurrentFrame():getParent() or mw.getCurrentFrame() local maybe_md = options.plain and utils.identity or frontend.markdown local tohtml = function(str) return frame:preprocess(maybe_md(str)) end -- Disable edit sections for automatic documentation pages. if not options.code then documentation:wikitext(frame:preprocess('__NOEDITSECTION__')) end -- Information if not options.code and options.box then local custom, infobox = pcall(require, 'Module:Docbunto/infobox') if custom and type(infobox) == 'function' then documentation:wikitext(infobox(data, codepage, frame, options, title, maybe_md)):newline() end end -- Documentation lede. if not options.code and (#(data.summary or '') + #data.description) ~= 0 then local separator = #(data.summary or '') ~= 0 and #data.description ~= 0 and (data.description:find(patterns.DOCBUNTO_WIKITEXT) and '\n\n' or patterns.DOCBUNTO_CONCAT) or '' local intro = utils.join{ data.summary or '', separator, data.description } intro = tohtml(intro:gsub('^(' .. codepage .. ')', mw.text.tag('b', nil, '%1'))) if options.migration then documentation:wikitext(frame:preprocess(mw.text.tag{ name = 'syntaxhighlight', content = intro })):newline():newline() else documentation:wikitext(intro):newline():newline() end end -- Custom documentation preface. if options.preface then documentation:wikitext(options.preface):newline():newline() end -- Start code documentation. local codedoc = mw.html.create() local function_module = data.tags['param'] or data.tags['return'] local header_type = documentation.type == 'classmod' and 'class' or function_module and 'function' or 'items' if (function_module or #data.items ~= 0) and not options.code or options.preface then codedoc :tag('h2') :wikitext(i18n:msg('header-documentation')) :done() :newline() end if (function_module or #data.items ~= 0) then codedoc :tag('h3') :wikitext(i18n:msg('header-' .. header_type)) :done() :newline() end -- Function module support. if function_module then data.type, data.types[1] = 'function', 'function' options.item_id = data.name if not options.code or not options.verbose then data.description = '' end frontend.render_item(codedoc, data, options, frontend.pretask_function_name) if not options.simple and data.tags['param'] then frontend.render_tag(codedoc, 'param', data.tags['param'], options, frontend.pretask_variable_prefix) end if not options.simple and data.tags['error'] then frontend.render_tag(codedoc, 'error', data.tags['error'], options, frontend.pretask_error_line) end if not options.simple and data.tags['return'] then frontend.render_tag(codedoc, 'return', data.tags['return'], options) end end -- Render documentation items. local other_header = false local private_header = false local inaccessible for _, item in ipairs(data.items) do inaccessible = item.tags['local'] or item.tags['private'] if not options.all and inaccessible then break end if not other_header and item.type ~= 'section' and item.type ~= 'type' and not item.export and not item.hierarchy and not inaccessible then codedoc :tag('h3') :wikitext(i18n:msg('header-other')) :done() :newline() other_header = true end if not private_header and options.all and inaccessible then codedoc :tag('h3') :wikitext(i18n:msg('header-private')) :done() :newline() private_header = true end if item.type == 'section' then codedoc :tag('h3') :wikitext((mw.ustring.gsub(item.summary or item.alias or item.name, '[.։。।෴۔።]$', ''))) :done() :newline() if #item.description ~= 0 then codedoc:wikitext(mw.text.trim(item.description)):newline() end elseif item.type == 'type' then if options.strip and item.export and item.hierarchy then if item.alias then item.alias = item.alias:gsub('^[%w_]+[.[]?', '') else item.name = item.name:gsub('^[%w_]+[.[]?', '') end end codedoc :tag('h3') :wikitext(mw.text.tag('code', {}, item.alias or item.name)) :done() :newline() if (#(item.summary or '') + #item.description) ~= 0 then local separator = #(item.summary or '') ~= 0 and #item.description ~= 0 and (item.description:find(patterns.DOCBUNTO_WIKITEXT) and '\n\n' or patterns.DOCBUNTO_CONCAT) or '' codedoc:wikitext((item.summary or '') .. separator .. item.description):newline() end elseif item.type == 'function' then options.item_id = item.alias or item.name frontend.render_item(codedoc, item, options, frontend.pretask_function_name) if not options.simple and item.tags['param'] then frontend.render_tag(codedoc, 'param', item.tags['param'], options, frontend.pretask_variable_prefix) end if not options.simple and item.tags['error'] then frontend.render_tag(codedoc, 'error', item.tags['error'], options, frontend.pretask_error_line) end if not options.simple and item.tags['return'] then frontend.render_tag(codedoc, 'return', item.tags['return'], options) end elseif item.type == 'table' or item.type ~= nil and ( item.type == 'member' or item.type == 'variable' ) and (item.alias or item.name) then options.item_id = item.alias or item.name frontend.render_item(codedoc, item, options) if not options.simple and item.tags['field'] then frontend.render_tag(codedoc, 'field', item.tags['field'], options, frontend.pretask_variable_prefix) end end if item.type ~= 'section' and item.type ~= 'type' then if not options.simple and item.tags['note'] then frontend.render_tag(codedoc, 'note', item.tags['note'], options) end if not options.simple and item.tags['warning'] then frontend.render_tag(codedoc, 'warning', item.tags['warning'], options) end if not options.simple and item.tags['fixme'] then frontend.render_tag(codedoc, 'fixme', item.tags['fixme'], options) end if not options.simple and item.tags['todo'] then frontend.render_tag(codedoc, 'todo', item.tags['todo'], options) hasTodoItems = true end if not options.simple and item.tags['usage'] then frontend.render_tag(codedoc, 'usage', item.tags['usage'], options, frontend.pretask_usage_highlight) end if not options.simple and item.tags['see'] then frontend.render_tag(codedoc, 'see', item.tags['see'], options) end end end -- Render module-level annotations. local header_level = options.code and 'h3' or 'h2' local header_text for _, tag_name in ipairs(p.tags._annotation_hierarchy) do if data.tags[tag_name] then header_text = i18n:msg('tag-' .. tag_name, data.tags[tag_name].value and '1' or '2') header_text = '<' .. header_level .. '>' .. ' ' .. header_text .. ' ' .. '</' .. header_level .. '>' codedoc:newline():wikitext(header_text):newline() if data.tags[tag_name].value then codedoc:wikitext(data.tags[tag_name].value):newline() else for _, tag_el in ipairs(data.tags[tag_name]) do codedoc:wikitext('* ' .. tag_el.value):newline() end end end end -- Byline for autogeneration. if options.footer then codedoc:newline() codedoc:tag('hr'):done() codedoc:newline() codedoc:tag('p'):css( 'text-align', 'right' ):wikitext(i18n:msg('message-autogeneration')) end -- Add nowiki tags for EOF termination in tests. codedoc:tag('nowiki', { selfClosing = true }) -- Code documentation formatting. codedoc = tohtml(tostring(codedoc)) documentation:wikitext(codedoc) -- Endmatter table for module information. if options.card then local endmatter = mw.html.create('table') endmatter:addClass('wikitable') endmatter:css('width', '75%') endmatter:css('margin', '0 auto 1em auto') local module_type = data.type == 'module' and i18n:msg('invocable-module') or i18n:msg('meta-module') endmatter :tag('caption') :wikitext('Module information card (autogenerated)') endmatter :tag('tr') :tag('th'):attr('scope', 'col'):wikitext('Name'):done() :tag('th'):attr('scope', 'col'):wikitext('Value'):done() if options.image or data.info and data.info['image'] then endmatter :tag('tr') :tag('td'):attr('scope', 'row'):wikitext(i18n:msg('endmatter-image')):done() :tag('td') :attr('align', 'center') :wikitext('[[File:' .. (options.image or data.info['image']) .. '|270px|center]]') :done() :done() end if options.caption or data.info and data.info['caption'] then endmatter :tag('tr') :tag('td'):attr('scope', 'row'):wikitext(i18n:msg('endmatter-caption')):done() :tag('td') :attr('align', options.image or data.info['image'] and 'center' or 'left') :wikitext(tohtml(options.caption or data.info['caption'])) :done() :done() end endmatter :tag('tr') :tag('td'):attr('scope', 'row'):wikitext((gsub(i18n:msg('type-variable'), '^%w', mw.ustring.upper))):done() :tag('td') :attr('align', options.image or data.info and data.info['image'] and 'center' or 'left') :wikitext(mw.text.tag({ name = options.image or data.info and data.info['image'] and 'h1' or 'span', content = mw.text.tag({ name = 'b', content = mw.text.tag({ name = 'code', content = data.name or DEFAULT_VARIABLE }) }) })):done() :done() :tag('tr') :tag('td'):wikitext(i18n:msg('code')):done() :tag('td'):wikitext(tohtml('[[' .. data.filename .. ']]')):done() :done() if data.info and data.info['release'] then endmatter :tag('tr') :tag('td'):attr('scope', 'row'):wikitext(i18n:msg('release-status')):done() :tag('td'):wikitext(tohtml(data.info['release'])):done() :done() end if data.summary then endmatter :tag('tr') :tag('td'):attr('scope', 'row'):wikitext(i18n:msg('description')):done() :tag('td'):wikitext(tohtml(data.summary)):done() :done() end if data.info and data.info['author'] then endmatter :tag('tr') :tag('td'):attr('scope', 'row'):wikitext(i18n:msg('author')):done() :tag('td'):newline():wikitext(tohtml(data.info['author'])):done() :done() end if data.info and data.info['attribution'] then endmatter :tag('tr') :tag('td'):attr('scope', 'row'):wikitext(i18n:msg('using-code-by')):done() :tag('td'):wikitext(tohtml(data.info['attribution'])):done() :done() end if data.info and data.info['credit'] then endmatter :tag('tr') :tag('td'):attr('scope', 'row'):wikitext(i18n:msg('other-attribution')):done() :tag('td'):wikitext(tohtml(data.info['credit'])):done() :done() end if data.info and data.info['require'] then data.info['require'] = data.info['require'] :gsub('^[^[%s]+$', '[[%1]]') :gsub('%* ([^[%s]+)', '* [[%1]]') endmatter :tag('tr') :tag('td'):attr('scope', 'row'):wikitext(i18n:msg('dependencies')):done() :tag('td'):newline():wikitext(tohtml(data.info['require'])):done() :done() end if data.info and data.info['demo'] then endmatter :tag('tr') :tag('td'):attr('scope', 'row'):wikitext(i18n:msg('examples')):done() :tag('td'):wikitext(tohtml(data.info['demo'])):done() :done() end endmatter:newline() endmatter = tostring(endmatter) documentation:newline():newline() documentation:wikitext(endmatter) end documentation = tostring(documentation) if hasTodoItems and options.autodoc and mw.title.new('Module:' .. options.file).fullText == mw.title.getCurrentTitle().fullText then documentation = documentation .. '[[Category:' .. i18n:msg('todo') .. ']]' end if mw.title.new('Module:Docbunto/styles.css').exists then documentation = documentation .. frame:extensionTag('templatestyles', '', {src = 'Module:Docbunto/styles.css'}) end return documentation end --- Token dictionary for Docbunto tags. -- Maps Docbunto tag names to tag tokens. -- * Multi-line tags use the `'M'` token. -- * Multi-line preformatted tags use the `'ML'` token. -- * Identifier tags use the `'ID'` token. -- * Single-line tags use the `'S'` token. -- * Flags use the `'N'` token. -- * Type tags use the `'T'` token. -- @table p.tags p.tags = { -- Item-level tags, available for global use. ['param'] = 'M', ['see'] = 'M', ['note'] = 'M', ['usage'] = 'ML', ['description'] = 'M', ['field'] = 'M', ['return'] = 'M', ['fixme'] = 'M', ['todo'] = 'M', ['warning'] = 'M', ['error'] = 'M'; ['class'] = 'ID', ['name'] = 'ID', ['alias'] = 'ID'; ['summary'] = 'S', ['pragma'] = 'S', ['factory'] = 'S', ['release'] = 'S', ['author'] = 'S', ['copyright'] = 'S', ['license'] = 'S', ['image'] = 'S', ['caption'] = 'S', ['require'] = 'S', ['attribution'] = 'S', ['credit'] = 'S', ['demo'] = 'S'; ['local'] = 'N', ['export'] = 'N', ['private'] = 'N', ['constructor'] = 'N', ['static'] = 'N'; -- Project-level tags, all scoped to a file. ['module'] = 'T', ['script'] = 'T', ['classmod'] = 'T', ['topic'] = 'T', ['submodule'] = 'T', ['example'] = 'T', ['file'] = 'T'; -- Module-level tags, used to register module items. ['function'] = 'T', ['table'] = 'T', ['member'] = 'T', ['variable'] = 'T', ['section'] = 'T', ['type'] = 'T'; } p.tags._alias = { -- Normal aliases. ['about'] = 'summary', ['abstract'] = 'summary', ['brief'] = 'summary', ['bug'] = 'fixme', ['argument'] = 'param', ['credits'] = 'credit', ['code'] = 'usage', ['details'] = 'description', ['discussion'] = 'description', ['exception'] = 'error', ['lfunction'] = 'function', ['package'] = 'module', ['property'] = 'member', ['raise'] = 'error', ['requires'] = 'require', ['returns'] = 'return', ['throws'] = 'error', ['typedef'] = 'type', -- Typed aliases. ['bool'] = 'field', ['func'] = 'field', ['int'] = 'field', ['number'] = 'field', ['string'] = 'field', ['tab'] = 'field', ['vararg'] = 'param', ['tfield'] = 'field', ['tparam'] = 'param', ['treturn'] = 'return' } p.tags._type_alias = { -- Implicit type value alias. ['bool'] = 'boolean', ['func'] = 'function', ['int'] = 'number', ['number'] = 'number', ['string'] = 'string', ['tab'] = 'table', ['vararg'] = '...', -- Pure typed modifier alias. ['tfield'] = 'variable', ['tparam'] = 'variable', ['treturn'] = 'variable' } p.tags._project_level = { -- Contains code. ['module'] = true, ['script'] = true, ['classmod'] = true, ['submodule'] = true, ['file'] = true, -- Contains documentation. ['topic'] = true, ['example'] = true } p.tags._code_types = { ['module'] = true, ['script'] = true, ['classmod'] = true } p.tags._module_info = { ['image'] = true, ['caption'] = true, ['release'] = true, ['author'] = true, ['copyright'] = true, ['license'] = true, ['require'] = true, ['credit'] = true, ['attribution'] = true, ['demo'] = true } p.tags._annotation_tags = { ['field'] = true, ['warning'] = true, ['fixme'] = true, ['note'] = true, ['todo'] = true, ['see'] = true } p.tags._annotation_hierarchy = { 'warning', 'fixme', 'note', 'todo', 'see' } p.tags._privacy_tags = { ['private'] = true, ['local'] = true } p.tags._generic_tags = { ['variable'] = true, ['member'] = true } p.tags._subtype_tags = { ['factory'] = true, ['local'] = true, ['private'] = true, ['constructor'] = true, ['static'] = true } p.tags._subtype_hierarchy = { 'private', 'local', 'static', 'factory', 'constructor' } return p 97e0wti9mg3650r9kw1xqydjisw6dtc 803403 803402 2026-07-29T23:41:08Z SM7 3953 1 revision imported from [[:en:Module:Docbunto]] 803402 Scribunto text/plain --- Docbunto is an automatic documentation generator for Scribunto modules. -- The module is based on LuaDoc and LDoc. It produces documentation in -- the form of MediaWiki markup, using `@tag`-prefixed comments embedded -- in the source code of a Scribunto module. The taglet parser & doclet -- renderer Docbunto uses are also publicly exposed to other modules. -- -- Docbunto code items are introduced by a block comment (`--[[]]--`), an -- inline comment with three hyphens (`---`), or an inline `@tag` comment. -- The module can use static code analysis to infer variable names, item -- privacy (`local` keyword), tables (`{}` constructor) and functions -- (`function` keyword). MediaWiki and Markdown formatting is supported. -- -- Items are usually rendered in the order they are defined, if they are -- public items, or emulated classes extending the Lua primitives. There -- are many customisation options available to change Docbunto behaviour. -- -- @module docbunto -- @alias p -- @require Module:I18n -- @require Module:Lua_lexer -- @require Module:Unindent -- @require Module:Yesno -- @require Module:Arguments -- @require Module:Documentation -- @author [[wikia:dev:User:8nml|8nml]] (Fandom Dev Wiki) -- @author [[User:Awesome Aasim|Awesome Aasim]] (Wikimedia port) -- @attribution [https://github.com/stevedonovan @stevedonovan] ([https://github.com/stevedonovan/LDoc GitHub]) -- @release beta -- <nowiki> local p = {} -- Module dependencies. local title = mw.title.getCurrentTitle() local i18n = require('Module:I18n').loadMessages('Docbunto'):useContentLang() local references = mw.loadData('Module:Docbunto/references') local lexer = require('Module:Lua lexer') local unindent = require('Module:Unindent') local yesno = require('Module:Yesno') local doc = require('Module:Documentation') local fileTooBig local usingOptionsContent local hasTodoItems -- Module variables. local DEFAULT_TITLE = title.namespace == 828 and doc.getEnvironment({}).templateTitle.text or '' local DEFAULT_VARIABLE = DEFAULT_TITLE :gsub('^%u', mw.ustring.lower) :gsub('%s', '_'):gsub('%p', '.') local frame, gsub, match local syntaxhighlightPresent = mw.getCurrentFrame() :preprocess("<syntaxhighlight lang=\"lua\">-- Lua</syntaxhighlight>") ~= "<syntaxhighlight lang=\"lua\">-- Lua</syntaxhighlight>" -- Docbunto variables & tag tokens. local TAG_MULTI = 'M' local TAG_ID = 'ID' local TAG_SINGLE = 'S' local TAG_TYPE = 'T' local TAG_FLAG = 'N' local TAG_MULTI_LINE = 'ML' -- Docbunto processing patterns. local patterns = {} --- Configuration options. -- @table options -- @field[opt] {boolean} options.all Include local items in -- documentation. -- @field[opt] {boolean} options.autodoc Whether the module is being -- called to automatically provide missing documentation. -- @field[opt] {boolean} options.boilerplate Removal of -- boilerplate (license block comments). -- @field[opt] {string} options.caption Infobox image caption. -- @field[opt] {boolean} options.code Only document Docbunto code -- items - exclude article infobox and lede from -- rendered documentation. Permits article to be -- edited in VisualEditor. -- @field[opt] {boolean} options.colon Format tags with a `:` suffix -- and without the `@` prefix. This bypasses the "doctag -- soup" some authors complain of. -- @field[opt] {string} options.image Infobox image. -- @field[opt] {boolean} options.box Include infobox in output. -- @field[opt] {boolean} options.footer Append a horizontal line & -- "generated by Docbunto" to the bottom of the output. -- @field[opt] {boolean} options.noluaref Don't link to the [[mw: -- Extension:Scribunto/Lua reference manual|Lua reference manual]] for types. -- @field[opt] {boolean} options.card Include endmatter card in the -- output at the end of the documentation. -- @field[opt] {boolean} options.plain Disable Markdown formatting -- in documentation. -- @field[opt] {string} options.preface Preface text to insert -- between lede & item documentation, used to provide -- usage and code examples. -- @field[opt] {boolean} options.simple Limit documentation to -- descriptions only. Removes documentation of -- subitem tags such as `@param` and `@field` ([[#Item -- subtags|see list]]). -- @field[opt] {boolean} options.sort Sort documentation items in -- alphabetical order. -- @field[opt] {boolean} options.strip Remove table index in -- documentation. -- @field[opt] {boolean} options.ulist Indent subitems as `<ul>` -- lists (LDoc/JSDoc behaviour). -- @field[opt] {boolean} options.verbose Show the code -- description of a package function when documenting -- function modules. -- Docbunto private utilities. local utils = {} --- @{string.find} optimisation for @{string} functions. -- @function utils.strfind_wrap -- @param {function} strfunc String library function. -- @return {function} Function wrapped in @{string.find} check. -- @local function utils.strfind_wrap(func) return function(...) local arg = {...} if string.find(arg[1], arg[2]) then return func(...); end end end --- String concatenation utility. -- @function join -- @param {sequence<string>} tbl Table of string segments -- @return {function} A concatenated string. -- @local function utils.join(tbl) return table.concat(tbl, '') end --- Pattern configuration function. -- Resets patterns for each documentation build. -- @function utils.configure_patterns -- @param {options} options Configuration options. -- @local function utils.configure_patterns(options) -- Setup Unicode or ASCII character encoding (optimisation). gsub = utils.strfind_wrap( options.unicode and mw.ustring.gsub or string.gsub ) match = utils.strfind_wrap( options.unicode and mw.ustring.match or string.match ) patterns.DOCBUNTO_SUMMARY = options.iso639_th and '^[^ ]+' or options.unicode and '^[^.։。।෴۔።]+[.։。।෴۔።]?' or '^[^.]+%.?' patterns.DOCBUNTO_CONCAT = options.iso639_th and '' or ' ' patterns.DOCBUNTO_TYPE = '^{({*[^}]+}*)}%s*' patterns.DOCBUNTO_WIKITEXT = '^[{|!}:#*=]+[%s-}]+' -- Setup parsing tag patterns with colon mode support. patterns.DOCBUNTO_TAG = options.colon and '^%s*(%w+):' or '^%s*@(%w+)' patterns.DOCBUNTO_TAG_COMMENT = '^[-%s]*' .. patterns.DOCBUNTO_TAG:sub(2) patterns.DOCBUNTO_TAG_VALUE = patterns.DOCBUNTO_TAG .. '(.*)' patterns.DOCBUNTO_TAG_MOD_VALUE = patterns.DOCBUNTO_TAG .. '%[([^%]]*)%](.*)' patterns.DOCBUNTO_TAG_OPTION_VALUE = '(%[[^%]]*%])%s*(.*)' end --- Tag processor function. -- @function utils.process_tag -- @param {string} str Tag string to process. -- @return {table} Tag object. -- @local function utils.process_tag(str) local tag = {} if str:find(patterns.DOCBUNTO_TAG_MOD_VALUE) then tag.name, tag.modifiers, tag.value = str:match(patterns.DOCBUNTO_TAG_MOD_VALUE) local modifiers = {} for m in tag.modifiers:gmatch('[^%s,]+') do local i = m:find('=') if i then local key = mw.text.trim(m:sub(1, i - 1)) local val = mw.text.trim(m:sub(i + 1, #m)) modifiers[key] = val else modifiers[m] = true end end if modifiers.optchain then modifiers.opt = true modifiers.optchain = nil end tag.modifiers = modifiers else tag.name, tag.value = str:match(patterns.DOCBUNTO_TAG_VALUE) end tag.value = mw.text.trim(tag.value) if p.tags._type_alias[tag.name] then if p.tags._type_alias[tag.name] ~= 'variable' then tag.value = p.tags._type_alias[tag.name] .. ' ' .. tag.value tag.name = 'field' end if tag.value:match('^%S+') ~= '...' then tag.value = tag.value:gsub('^(%S+)', '{%1}') end end tag.name = p.tags._alias[tag.name] or tag.name if tag.name ~= 'usage' and tag.value:find(patterns.DOCBUNTO_TYPE) then tag.type = tag.value:match(patterns.DOCBUNTO_TYPE) if tag.type:find('^%?') then tag.type = tag.type:sub(2) .. '|nil' end tag.value = tag.value:gsub(patterns.DOCBUNTO_TYPE, '') end if (tag.name == 'param' or tag.name == 'field') and tag.value:find(patterns.DOCBUNTO_TAG_OPTION_VALUE) then tag.value = tag.value:gsub(patterns.DOCBUNTO_TAG_OPTION_VALUE, '%1 %2') tag.modifiers = tag.modifiers or {} tag.modifiers.opt = true end if p.tags[tag.name] == TAG_FLAG then tag.value = true end return tag end --- Module info extraction utility. -- @function utils.extract_info -- @param {table} documentation Package doclet info. -- @return {table} Information name-value map. -- @local function utils.extract_info(documentation) local info = {} for _, tag in ipairs(documentation.tags) do if p.tags._module_info[tag.name] then if info[tag.name] then if not info[tag.name]:find('^%* ') then info[tag.name] = '* ' .. info[tag.name] end info[tag.name] = info[tag.name] .. '\n* ' .. tag.value else info[tag.name] = tag.value end end end return info end --- Type extraction utility. -- @function utils.extract_types -- @param {table} item Item documentation data. -- @return {sequence<string>} Item types. -- @local function utils.extract_types(item) local item_types = {} for _, tag in ipairs(item.tags) do if p.tags[tag.name] == TAG_TYPE then item_types[1] = tag.name if tag.name == 'variable' then local implied_local = utils.process_tag('@local') table.insert(item.tags, mw.clone(implied_local)) item.tags['local'] = mw.clone(implied_local) end if p.tags._generic_tags[item_types[1]] and not p.tags._project_level[item_types[1]] and tag.type then item_types[#item_types + 1] = tag.type end break end end return item_types end --- Name extraction utility. -- @function utils.extract_name -- @param {table} item Item documentation data. -- @param {boolean} project Whether the item is project-level. -- @return {string} Item name. -- @local function utils.extract_name(item, opts) opts = opts or {} local item_name for _, tag in ipairs(item.tags) do if p.tags[tag.name] == TAG_TYPE then item_name = tag.value; break end end if item_name or not opts.project then return item_name end item_name = item.code:match('\nreturn%s+([%w_]+)') if item_name == 'p' and not item.tags['alias'] then local implied_alias = { name = 'alias', value = 'p' } item.tags['alias'] = implied_alias table.insert(item.tags, implied_alias) end item_name = (item_name and item_name ~= 'p') and item_name or item.filename :gsub('^' .. mw.site.namespaces[828].name .. ':', '') :gsub('^(%u)', mw.ustring.lower) :gsub('/', '.'):gsub(' ', '_') return item_name end --- Source code utility for item name detection. -- @function utils.deduce_name -- @param {string} tokens Stream tokens for first line. -- @param {string} index Stream token index. -- @param {table} opts Configuration options. -- @param[opt] {boolean} opts.lookahead Whether a variable name succeeds the index. -- @param[opt] {boolean} opts.lookbehind Whether a variable name precedes the index. -- @return {string} Item name. -- @local function utils.deduce_name(tokens, index, opts) local name = '' if opts.lookbehind then for i2 = index, 1, -1 do if tokens[i2].type ~= 'keyword' then name = tokens[i2].data .. name else break end end elseif opts.lookahead then for i2 = index, #tokens do if tokens[i2].type ~= 'keyword' and not tokens[i2].data:find('^%(') then name = name .. tokens[i2].data else break end end end return name end --- Parses Lua item names to extract a hierarchy. --- @function utils.parse_hierarchy -- @param {string} item_name Documentation item name. -- @return {sequence<string>} List of global keys to index the -- variable reference. -- @local function utils.parse_hierarchy(item_name) local hierarchy = {} for _, lexeme in ipairs(lexer(item_name)) do if lexeme.type == 'ident' or lexeme.type == 'string' or lexeme.type == 'number' then hierarchy[#hierarchy + 1] = lexeme.text end end return hierarchy end --- Code analysis utility. -- @function utils.code_heuristic -- @param {table} item Item documentation data. -- @local function utils.code_heuristic(item) local is_section = item.tags['private'] or item.tags['local'] or item.type == 'type' if item.name and item.type and is_section then return end local tokens = lexer(item.code:match('^[^\n]*'))[1] local t, i = tokens[1], 1 local item_name, item_type local lexemes = {} while t do if t.type ~= 'whitespace' then lexemes[#lexemes + 1] = tokens[i] end t, i = tokens[i + 1], i + 1 end t, i = lexemes[1], 1 while t do if t.data == '=' and not item.name then item_name = utils.deduce_name(lexemes, i - 1, { lookbehind = true }) end if t.data == 'function' and not item.name then item_type = 'function' if lexemes[i + 1] and lexemes[i + 1].data ~= '(' then item_name = utils.deduce_name(lexemes, i + 1, { lookahead = true }) end end if t.data == '{' or t.data == '{}' and not item.type then item_type = 'table' end if t.data == 'local' and not is_section then local implied_local = utils.process_tag('@local') table.insert(item.tags, implied_local) item.tags['local'] = mw.clone(implied_local) end t, i = lexemes[i + 1], i + 1 end item.name = item.name or item_name or '' item.type = item.type or item_type if #(item.types or {}) == 0 then item.types[1] = item.type end end --- Array hash map conversion utility. -- @function utils.hash_map -- @param {table} item Item documentation data array. -- @return {table} Item documentation data map. -- @local function utils.hash_map(array) local map = array for _, element in ipairs(array) do if map[element.name] and not map[element.name].name then table.insert(map[element.name], mw.clone(element)) elseif map[element.name] and map[element.name].name then map[element.name] = { map[element.name], mw.clone(element) } else map[element.name] = mw.clone(element) end end return map end --- Item export utility. -- @function utils.export_item -- @param {table} documentation Package documentation data. -- @param {string} item_reference Identifier name for item. -- @param {string} item_index Identifier name for item. -- @param {string} item_alias Export alias for item. -- @param {boolean} factory_item Whether the documentation item is a factory function. -- @local function utils.export_item(documentation, item_reference, item_index, item_alias, factory_item) for _, item in ipairs(documentation.items) do if item_reference == item.name then item.tags['local'] = nil item.tags['private'] = nil for index, tag in ipairs(item.tags) do if p.tags._privacy_tags[tag.name] then table.remove(item.tags, index) end end item.type, item.types[1] = 'member', 'member' local accessor if item_alias:find('^%[') then accessor = '' else local is_class = documentation.type == 'classmod' or factory_item if is_class and not item.tags['static'] then accessor = ':' else accessor = '.' end end if factory_item then item.alias = utils.join{ documentation.items[item_index].tags['factory'].value, accessor, item_alias } else item.alias = utils.join{ ((documentation.tags['alias'] or {}).value or documentation.name), accessor, item_alias } end item.hierarchy = utils.parse_hierarchy(item.alias) break end end end --- Subitem tag correction utility. -- @function utils.correct_subitem_tag -- @param {table} item Item documentation data. -- @local function utils.correct_subitem_tag(item) local field_tag = item.tags['field'] if item.type ~= 'function' or not field_tag then return end if field_tag.name then field_tag.name = 'param' else for _, tag_el in ipairs(field_tag) do tag_el.name = 'param' end end local param_tag = item.tags['param'] if param_tag and not param_tag.name then if field_tag.name then table.insert(param_tag, field_tag) else for _, tag_el in ipairs(field_tag) do table.insert(param_tag, tag_el) end end elseif param_tag and param_tag.name then if field_tag.name then param_tag = { param_tag, field_tag } else for i, tag_el in ipairs(field_tag) do if i == 1 then param_tag = { param_tag } end for _, tag_el in ipairs(field_tag) do table.insert(param_tag, tag_el) end end end else param_tag = field_tag end item.tags['field'] = nil end --- Item override tag utility. -- @function utils.override_item_tag -- @param {table} item Item documentation data. -- @param {string} name Tag name. -- @param[opt] {string} alias Target alias for tag. -- @local function utils.override_item_tag(item, name, alias) if item.tags[name] then item[alias or name] = item.tags[name].value end end --- Identity function. -- @function utils.identity -- @param x The variable by reference. -- @return The parameter `x` by reference. -- @local function utils.identity(x) return x end --- Argument processing -- @param {string} funcName name of function -- @return function to process arguments -- @local local function makeInvokeFunc(funcName) return function (f) local args = require("Module:Arguments").getArgs(f, { valueFunc = function (key, value) if type(value) == 'string' then value = value:match('^%s*(.-)%s*$') -- Remove whitespace. if key == 'heading' or value ~= '' then return value else return nil end else return value end end }) return p[funcName](args) end end -- Docbunto rendering logic. local frontend = {} --- Markdown header converter. -- @function urils.markdown_header -- @param {string} hash Leading hash. -- @param {string} text Header text. -- @return {string} MediaWiki header. -- @local function frontend.markdown_header(hash, text) local symbol = '=' return utils.join{ '\n', symbol:rep(#hash), ' ', text, ' ', symbol:rep(#hash), '\n' } end --- Item reference formatting. -- @function urils.item_reference -- @param {string} ref Item reference. -- @return {string} Internal MediaWiki link to article item. -- @local function frontend.item_reference(ref) local temp = mw.text.split(ref, '|') local item = temp[1] local text = temp[2] or temp[1] if references.items[item] then local interwiki = 'mw' item = interwiki .. references.items[item] else item = '#' .. item end return mw.text.tag{ name = 'code', content = utils.join{ '[[', item, '|', text, ']]' } } end --- Doclet type reference preprocessor. -- Formats types with links to the [[mw:Extension:Scribunto/Lua reference manual|Lua reference manual]]. -- @function frontend.type_reference -- @param {table} item Item documentation data. -- @param {options} options Configuration options. -- @local function frontend.type_reference(item, options) local interwiki = 'mw:' if not options.noluaref and item.value and item.value:match('^%S+') == mw.text.tag('code', nil, '...') then item.value = item.value:gsub('^(%S+)', mw.text.tag{ name = 'code', content = utils.join{ '[[', interwiki, 'Lua reference manual#varargs|...]]' } }) end -- Handle tag type references. item.types = item.types or item.type and { item.type } if not item.types then return end for index, item_type in ipairs(item.types) do item.types[index] = item.types[index]:gsub('[%w_. ]+', function(item_type) local data = references.types[item_type] local name = data and data.name or item_type if not name:match('%.') and not name:match('^%u') and data then name = i18n:msg('type-' .. name) end if data and not options.noluaref then return utils.join{ '[[', interwiki, data.link, '|', name , ']]' } elseif item_type:find(i18n:msg('line-no'):gsub('$1', '')) then return utils.join { '[[' , options.file , '#L-', item_type:sub(6), '|', name, ']]' } elseif not options.noluaref and not p.tags._generic_tags[item_type] then return utils.join { '[[#', item_type, '|', name, ']]' } end end) end item.type = table.concat(item.types, i18n:msg('separator-dot')) end --- Markdown preprocessor to MediaWiki format. -- @function frontend.markdown -- @param {string} str Unprocessed Markdown string. -- @return {string} MediaWiki-compatible markup with HTML formatting. -- @local function frontend.markdown(str) -- Bold & italic tags. str = str:gsub('%*%*%*([^\n*]+)%*%*%*', '<b><i>%1</i></b>') str = str:gsub('%*%*([^\n*]+)%*%*', '<b>%1</b>') str = str:gsub('%*([^\n*]+)%*', '<i>%1</i>') -- Self-closing header support. str = str:gsub('%f[^\n%z](#+) *([^\n#]+) *#+%s', frontend.markdown_header) -- External and internal links. str = str:gsub('%[([^\n%]]+)%]%(([^\n][^\n)]-)%)', '[%2 %1]') str = str:gsub('%{@link%s+([^}]+)%}', '[%1]') str = str:gsub('%@{([^\n}]+)}', frontend.item_reference) -- Programming & scientific notation. str = str:gsub('%f["`]`([^\n`]+)`%f[^"`]', '<code><nowiki>%1</nowiki></code>') str = str:gsub('%$%$([^\n$]+)%$%$', '<math display="inline">%1</math>') -- Strikethroughs. str = str:gsub('~~([^\n~]+)~~', '<del>%1</del>') -- HTML output. return str end --- Doclet function item preprocessor. -- Formats item name as a function call with top-level arguments. -- @function frontend.pretask_function_name -- @param {table} item Item documentation data. -- @param {options} options Configuration options. -- @local function frontend.pretask_function_name(item, options) local target = item.alias and 'alias' or 'name' item[target] = item[target] .. '(' if item.tags['param'] and item.tags['param'].value and not item.tags['param'].value:find('^[%w_]+[.[]') then if (item.tags['param'].modifiers or {})['opt'] then local optional = mw.html.create('span') optional:css('opacity', '0.65') optional:wikitext(item.tags['param'].value:match('^(%S+)')) item[target] = item[target] .. tostring(optional) else item[target] = item[target] .. item.tags['param'].value:match('^(%S*)') end elseif item.tags['param'] then for index, tag in ipairs(item.tags['param']) do if not tag.value:find('^[%w_]+[.[]') then if (tag.modifiers or {})['opt'] then local param = mw.html.create('span') param:css('opacity', '0.65') param:wikitext((index > 1 and ', ' or '') .. tag.value:match('^%S+')) item[target] = item[target] .. tostring(param) else item[target] = item[target] .. (index > 1 and ', ' or '') .. tag.value:match('^(%S+)') end end end end item[target] = item[target] .. ')' end --- Doclet parameter/field subitem preprocessor. -- Indents and wraps variable prefix with `code` tag. -- @function pretask_variable_prefix -- @param {table} item Item documentation data. -- @param {options} options Configuration options. -- @local function frontend.pretask_variable_prefix(item, options) local indent_symbol = options.ulist and '*' or ':' local indent_level, indentation, item_id if item.value then indent_level = item.value:match('^%S*') == '...' and 0 or select(2, item.value:match('^%S*'):gsub('[.[]', '')) indentation = indent_symbol:rep(indent_level) item_id = options.item_id item.value = indentation .. item.value:gsub( '^(%S+)', mw.text.tag{ name = 'code', attrs = { id = item_id .. '~%1' }, content = '%1' }, 1 ) elseif item then for _, item_el in ipairs(item) do frontend.pretask_variable_prefix(item_el, options) end end end --- Doclet usage subitem preprocessor. -- Formats usage example with `<syntaxhighlight>` tag. -- @function frontend.pretask_usage_highlight -- @param {table} item Item documentation data. -- @param {options} options Configuration options. -- @local function frontend.pretask_usage_highlight(item, options) if item.value then item.value = unindent(mw.text.trim(item.value)) if item.value:find('^{{.+}}$') then item.value = item.value:gsub('=', mw.text.nowiki) local multi_line = item.value:find('\n') and '|m = 1|' or '|' if item.value:match('^{{([^:]+)') == '#invoke' then item.value = item.value:gsub('^{{#invoke:', '{{mlx' .. multi_line) else if options.entrypoint then item.value = item.value:gsub('^([^|]+)|%s*([^|}]-)(%s*)([|}])','%1|"%2"%3%4') end item.value = item.value:gsub('^{{', '{{tlx' .. multi_line) end local highlight_class = tonumber(mw.site.currentVersion:match('^%d%.%d+')) > 1.19 and 'mw-highlight' or 'mw-geshi' if item.value:find('\n') then local highlight_div = mw.html.create('div') highlight_div.addClass(highlight_class) highlight_div.addClass('mw-content-ltr') highlight_div:attr('dir', 'ltr') highlight_div:wikitext(item.value) item.value = tostring(highlight_div) else item.value = mw.text.tag{ name = 'span', attrs = { class = 'code' }, content = item.value } end else local highlight_tag = mw.html.create('syntaxhighlight') highlight_tag:attr('lang', 'lua') if not item.value:find('\n') then highlight_tag:attr('inline', 'inline') end highlight_tag:wikitext(item.value) item.value = tostring(highlight_tag) end elseif item then for _, item_el in ipairs(item) do frontend.pretask_usage_highlight(item_el, options) end end end --- Doclet error subitem preprocessor. -- Formats line numbers (`{#}`) in error tag values. -- @function frontend.pretask_error_line -- @param {table} item Item documentation data. -- @local function frontend.pretask_error_line(item, options) if item.name then local line if item.modifiers and item.modifiers.line then line = item.modifiers.line end for mod in pairs(item.modifiers or {}) do if mod:find('^%d+$') then line = mod end end if line then if item.types then item.types[#item.types + 1] = i18n:msg('line-no', line) else item.type = i18n:msg('line-no', line) end end elseif item then for _, item_el in ipairs(item) do frontend.pretask_error_line(item_el, options) end end end --- Doclet item renderer. -- @function frontend.render_item -- @param {table} stream Wikitext documentation stream. -- @param {table} item Item documentation data. -- @param {options} options Configuration options. -- @param[opt] {function} pretask Item data preprocessor. -- @local function frontend.render_item(stream, item, options, pretask) if pretask then pretask(item, options) end local item_name = item.alias or item.name frontend.type_reference(item, options) local item_type = item.type for _, name in ipairs(p.tags._subtype_hierarchy) do if item.tags[name] then item_type = item_type .. i18n:msg('separator-dot') .. name end end item_type = i18n:msg('parentheses', item_type) if options.strip and item.export and item.hierarchy then item_name = item_name:gsub('^[%w_]+[.[]?', '') end -- add single spaces between parentheses similar to -- MediaWiki documentation if not item_name:find( '()', 1, true ) then item_name = item_name:gsub('%(', '( ') item_name = item_name:gsub('%)', ' )') end item_name = item_name:gsub('%[', '[ ') item_name = item_name:gsub('%]', ' ]') local code_node = mw.html.create('code') :attr('id', options.item_id) :wikitext(item_name) if not usingOptionsContent then local span_node = stream:wikitext(';') :tag('span') :addClass('plainlinks docbunto-link') if fileTooBig or not syntaxhighlightPresent then span_node:wikitext('[{{fullurl:' .. options.file .. '|action=edit#mw-ce-l' .. item.lineno .. '}} ' .. tostring(code_node) .. ']') elseif title.namespace == 828 and options.file == title.text then span_node:wikitext('[[' .. '#L-' .. item.lineno .. '|' .. tostring(code_node) .. ']]') else span_node:wikitext('[[' .. options.file .. '#L-' .. item.lineno .. '|' .. tostring(code_node) .. ']]') end else stream:wikitext(';' .. tostring(code_node)) end stream :wikitext(item_type) :newline() if (#(item.summary or '') + #item.description) ~= 0 then local separator = #(item.summary or '') ~= 0 and #item.description ~= 0 and (item.description:find(patterns.DOCBUNTO_WIKITEXT) and '\n' or patterns.DOCBUNTO_CONCAT) or '' local intro = (item.summary or '') .. separator .. item.description intro = intro :gsub('\n([{:#*])', '\n:%1') :gsub('\n\n([^=])', '\n:%1') stream:wikitext(':' .. intro):newline() end end --- Doclet tag renderer. -- @function frontend.render_tag -- @param {table} stream Wikitext documentation stream. -- @param {string} name Item tag name. -- @param {table} tag Item tag data. -- @param {options} options Configuration options. -- @param[opt] {function} pretask Item data preprocessor. -- @local function frontend.render_tag(stream, name, tag, options, pretask) if pretask then pretask(tag, options) end if tag.value then frontend.type_reference(tag, options) local tag_name = i18n:msg('tag-' .. name, '1') -- Handle ul/ol/dl comment support. stream:wikitext(':') :tag('b') :wikitext(tag_name) :done():done() :wikitext(i18n:msg('separator-semicolon')) :wikitext((mw.text.trim(tag.value):gsub('\n([{:#*])', '\n:%1'))) if tag.value:find('\n[{:#*]') and (tag.type or (tag.modifiers or {})['opt']) then stream:newline():wikitext(':') end -- Type and optional appositive for tag. if tag.type and (tag.modifiers or {})['opt'] then stream:wikitext(i18n:msg{ key = 'parentheses', args = { tag.type .. i18n:msg('separator-colon') .. i18n:msg('optional') } }) elseif tag.type then stream:wikitext(i18n:msg{ key = 'parentheses', args = { tag.type } }) elseif (tag.modifiers or {})['opt'] then stream:wikitext(i18n:msg{ key = 'parentheses', args = { i18n:msg('optional') } }) end stream:newline() else local tag_name = i18n:msg('tag-' .. name, tostring(#tag)) stream :wikitext(':') :tag('b') :wikitext(tag_name) :done():done() :wikitext(i18n:msg('separator-semicolon')) :newline() for _, tag_el in ipairs(tag) do frontend.type_reference(tag_el, options) local marker = options.ulist and '*' or ':' stream:wikitext(':' .. marker .. tag_el.value:gsub('\n([{:#*])', '\n:' .. marker .. '%1')) if tag_el.value:find('\n[{:#*]') and (tag_el.type or (tag_el.modifiers or {})['opt']) then stream:newline():wikitext(':' .. marker .. (tag_el.value:match('^[*:]+') or '')) end if tag_el.type and (tag_el.modifiers or {})['opt'] then stream:wikitext(i18n:msg{ key = 'parentheses', args = { tag_el.type .. i18n:msg('separator-colon') .. i18n:msg('optional') } }) elseif tag_el.type then stream:wikitext(i18n:msg{ key = 'parentheses', args = { tag_el.type } }) elseif (tag_el.modifiers or {})['opt'] then stream:wikitext(i18n:msg{ key = 'parentheses', args = { i18n:msg('optional') } }) end stream:newline() end end end --- Template entrypoint for [[Template:Docbunto]]. -- @function p._main -- @param {table} args Arguments -- @return {string} Module documentation output. function p._main(args) frame = mw.getCurrentFrame() local modname = mw.text.trim(args[1] or args.file or DEFAULT_TITLE) local options = {} options.file = modname options.all = yesno(args.all or false) options.autodoc = yesno(args.autodoc or false) options.boilerplate = yesno(args.boilerplate or options.autodoc or false) options.box = yesno(args.box or args.infobox or options.autodoc or false) options.caption = args.caption options.card = yesno(args.card or (title.namespace == 828 and not options.autodoc)) options.code = yesno(args.code or false) options.colon = yesno(args.colon or false) options.footer = yesno(args.footer or title.namespace == 828) options.image = args.image options.migration = yesno(args.migration or false) options.noluaref = yesno(args.noluaref or false) options.plain = yesno(args.plain or false) options.preface = args.preface options.simple = yesno(args.simple or false) options.sort = yesno(args.sort or false) options.strip = yesno(args.strip or false) options.verbose = yesno(args.verbose or false) options.ulist = yesno(args.ulist or false) return p.build(modname, options) end --- Entrypoint for the module. -- @function p.main -- @param {Frame} frame Module frame. -- @return {string} Module documentation output. p.main = makeInvokeFunc('_main') --- Scribunto documentation generator entrypoint. -- @function p.build -- @param[opt] {string} modname Module page name (without namespace). -- Default: second-level subpage. -- @param[opt] {options} options Configuration options. function p.build(modname, options) modname = modname or DEFAULT_TITLE options = options or {} local tagdata = p.taglet(modname, options) local docdata = p.doclet(tagdata, options) return docdata end --- Docbunto taglet parser for Scribunto modules. -- @function p.taglet -- @param[opt] {string} modname Module page name (without namespace). -- @param[opt] {options} options Configuration options. -- @error[line=1081] {string} 'Lua source code not found in $1' -- @error[line=1087] {string} 'documentation markup for Docbunto not found in $1' -- @return {table} Module documentation data. function p.taglet(modname, options) modname = modname or DEFAULT_TITLE options = options or {} local filepath = mw.site.namespaces[828].name .. ':' .. modname local content = mw.title.new(filepath):getContent() -- Content checks. if options.content then content = options.content usingOptionsContent = true else content = mw.title.new(filepath):getContent() or error(i18n:msg('no-content', filepath), 0) end -- Syntax highlighting is disabled for files above a specific size fileTooBig = content:len() > 102400 if not content:match('%-%-%-') and not content:match(options.colon and '%w+:' or '@%w+') then error(i18n:msg('no-markup', filepath)) end -- Remove leading escapes. content = content:gsub('^%-%-+%s*<[^>]+>\n', '') -- Remove closing pretty comments. content = content:gsub('\n%-%-%-%-%-+(\n[^-]+)', '\n-- %1') -- Remove boilerplate block comments. if options.boilerplate then content = content:gsub('^%-%-%[=*%[\n.-\n%-?%-?%]%=*]%-?%-?%s+', '') content = content:gsub('%s+%-%-%[=*%[\n.-\n%-?%-?%]%=*]%-?%-?$', '') end -- Configure patterns for colon mode and Unicode character encoding. options.unicode = type(content:find('[^%w%c%p%s]+')) == 'number' options.iso639_th = type(content:find('\224\184[\129-\155]')) == 'number' utils.configure_patterns(options) -- Content lexing. local lines = lexer(content) local tokens = {} local dummy_token = { data = '', posFirst = 1, posLast = 1 } local token_closure = 0 for _, line in ipairs(lines) do if #line == 0 then dummy_token.type = token_closure == 0 and 'whitespace' or tokens[#tokens].type table.insert(tokens, mw.clone(dummy_token)) else for _, token in ipairs(line) do if token.data:find('^%[=*%[$') or token.data:find('^%-%-%[=*%[$') then token_closure = 1 end if token.data:find(']=*]') then token_closure = 0 end table.insert(tokens, token) end end end -- Start documentation data. local documentation = {} documentation.filename = filepath documentation.description = '' documentation.code = content documentation.comments = {} documentation.tags = {} documentation.items = {} local line_no = 0 local item_index = 0 -- Taglet tracking variables. local start_mode = true local comment_mode = false local doctag_mode = false local export_mode = false local special_tag = false local pragma_mode = false local factory_mode = false local return_mode = false local comment_tail = '' local tag_name = '' local new_item = false local new_tag = false local new_tag_item = false local new_item_code = false local pretty_comment = false local comment_brace = false local t, i, item = tokens[1], 1 local p_success, p_error = pcall(function() while t do -- Taglet variable update. new_item = t.data:find('^%-%-%-') or t.data:find('^%-%-%[%=*%[$') comment_tail = t.data:gsub('^%-%-+', '') tag_name = comment_tail:match(patterns.DOCBUNTO_TAG) tag_name = p.tags._alias[tag_name] or tag_name new_tag = type(p.tags[tag_name]) == 'string' local last_token = t.posFirst ~= 1 and 2 or 1 new_tag_item = ( new_tag and i > 1 and tokens[i - last_token].type ~= 'comment' and not tokens[i - last_token].data:find(patterns.DOCBUNTO_TAG_COMMENT) ) pretty_comment = t.data:find('^%-%-%-+%s*$') or t.data:find('[^-]+%-%-%-+%s*$') or t.data:find('%-*[ \t]*<nowiki>$') or t.data:find('%-*[ \t]*<pre>$') comment_brace = t.data:find('^%-%-%[=*%[$') or t.data:find('^%-%-%]=*%]$') or t.data:find('^%]=*%]%-%-$') pragma_mode = tag_name == 'pragma' export_mode = tag_name == 'export' and not doctag_mode special_tag = pragma_mode or export_mode local tags, tag, subtokens, separator -- Line counter. if t.posFirst == 1 then line_no = line_no + 1 end -- Data insertion logic. if t.type == 'comment' then if new_item then comment_mode = true end if (new_tag or new_tag_item) and not special_tag then comment_mode = true doctag_mode = true end -- Module-level documentation taglet. if start_mode then table.insert(documentation.comments, t.data) end -- Module description. if start_mode and comment_mode and not new_tag and not doctag_mode and not pretty_comment and not comment_brace then separator = mw.text.trim(comment_tail):find(patterns.DOCBUNTO_WIKITEXT) and '\n' or (#documentation.description ~= 0 and patterns.DOCBUNTO_CONCAT or '') documentation.description = utils.join{ documentation.description, separator, mw.text.trim(comment_tail) } end -- Switch from module description to module-level tag list. if start_mode and new_tag and not export_mode then doctag_mode = true table.insert(documentation.tags, utils.process_tag(comment_tail)) -- Concatenating module-level tag with more information. elseif start_mode and doctag_mode and not pretty_comment and not comment_brace then tags = documentation.tags if p.tags[tags[#tags].name] == TAG_MULTI then separator = mw.text.trim(comment_tail):find(patterns.DOCBUNTO_WIKITEXT) and '\n' or patterns.DOCBUNTO_CONCAT tags[#tags].value = utils.join{ tags[#tags].value, separator, mw.text.trim(comment_tail) } elseif p.tags[tags[#tags].name] == TAG_MULTI_LINE then tags[#tags].value = utils.join{ tags[#tags].value, '\n', comment_tail } end end -- Detect a new documentation item. if not start_mode and (new_item or new_tag_item) and not special_tag then table.insert(documentation.items, {}) item_index = item_index + 1 item = documentation.items[item_index] item.lineno = line_no item.code = '' item.comments = {} item.description = '' item.tags = {} end -- Concatenate an item with more information. if not start_mode and comment_mode and not new_tag and not doctag_mode and not pretty_comment and not comment_brace then separator = (mw.text.trim(comment_tail):find(patterns.DOCBUNTO_WIKITEXT)) and '\n' or (#item.description ~= 0 and patterns.DOCBUNTO_CONCAT or '') item.description = utils.join{ item.description, separator, mw.text.trim(comment_tail) } end -- Switch from item information to item tags. if not start_mode and new_tag and not special_tag then doctag_mode = true table.insert(item.tags, utils.process_tag(comment_tail)) -- Extend item tags with lines of further information. elseif not start_mode and doctag_mode and not pretty_comment and not comment_brace then tags = item.tags if p.tags[tags[#tags].name] == TAG_MULTI then separator = mw.text.trim(comment_tail):find(patterns.DOCBUNTO_WIKITEXT) and '\n' or patterns.DOCBUNTO_CONCAT tags[#tags].value = utils.join{ tags[#tags].value, separator, mw.text.trim(comment_tail) } elseif p.tags[tags[#tags].name] == TAG_MULTI_LINE then tags[#tags].value = utils.join{ tags[#tags].value, '\n', comment_tail } end end -- Save pre-item comments to item information. if not start_mode and (comment_mode or doctag_mode) then table.insert(item.comments, t.data) end -- Export tag support. if export_mode then factory_mode = t.posFirst ~= 1 if factory_mode then item.exports = true else documentation.exports = true end -- Parse export token data while concatenating code. subtokens = {} while t and (not factory_mode or (factory_mode and t.data ~= 'end')) do if factory_mode then item.code = utils.join{ item.code, (t.posFirst == 1 and '\n' or ''), t.data } end -- Fetch our item export tokens aka "subtokens". t, i = tokens[i + 1], i + 1 if t and t.posFirst == 1 then line_no = line_no + 1 end if t and t.type ~= 'whitespace' and t.type ~= 'keyword' and t.type ~= 'comment' then table.insert(subtokens, t) end end -- Tracking and boolean variables for parsing exports. local separator = { [','] = true, [';'] = true } local brace = { ['{'] = true, ['}'] = true } local item_reference, item_alias = '', '' local sequence_index, has_key = 0, false local subtoken, index = subtokens[2], 2 -- Logic that exports items to an item or module. while not brace[subtoken.data] do if subtoken.data == '=' then has_key = true elseif not separator[subtoken.data] then if has_key then item_reference = item_reference .. subtoken.data else item_alias = item_alias .. subtoken.data end end if subtokens[index + 1] and separator[subtokens[index + 1].data] or brace[subtokens[index + 1].data] then if not has_key then sequence_index = sequence_index + 1 item_reference, item_alias = item_alias, item_reference item_alias = '[' .. tostring(sequence_index) .. ']' end utils.export_item(documentation, item_reference, item_index, item_alias, factory_mode) item_reference, item_alias, has_key = '', '', false end subtoken, index = subtokens[index + 1], index + 1 end if not factory_mode then break -- end of file else factory_mode = false -- end of item end end -- Pragma tag support. if pragma_mode then tag = utils.process_tag(comment_tail) if tag.modifiers then options[tag.value] = yesno((next((tag ).modifiers or {})), true) elseif tag.value:find(' ') then local index = tag.value:find(' ') local key = tag.value:sub(1, index - 1) local val = tag.value:sub(index + 1) options[key] = yesno(val) elseif options[tag.value] == nil then options[tag.value] = true end end end -- Package data post-processing. if (t.type ~= 'comment' or t.data:find('^%-%-%]=*%]$') or t.data:find('^%]=*%]%-%-$')) and (comment_mode or doctag_mode) and start_mode then documentation.tags = utils.hash_map(documentation.tags) documentation.name = utils.extract_name(documentation, { project = true }) documentation.info = utils.extract_info(documentation) documentation.types = utils.extract_types(documentation) or { 'module' } documentation.type = documentation.types[1] -- N.B: summary field = 1st comment sentence; description = text remainder. if #documentation.description ~= 0 then documentation.summary = match(documentation.description, patterns.DOCBUNTO_SUMMARY) documentation.description = documentation.description:sub(select(2, documentation.description:find('%s*', #documentation.summary + 1)) + 1) end documentation.description = documentation.description:gsub('%s%s+', '\n\n') documentation.executable = p.tags._code_types[documentation.type] and true or false utils.correct_subitem_tag(documentation) utils.override_item_tag(documentation, 'name') utils.override_item_tag(documentation, 'alias') utils.override_item_tag(documentation, 'summary') utils.override_item_tag(documentation, 'description') utils.override_item_tag(documentation, 'class', 'type') end -- Item data post-processing. if (t.type ~= 'comment' or t.data:find('^%-%-%]=*%]$') or t.data:find('^%]=*%]%-%-$')) and (comment_mode or doctag_mode) and item_index ~= 0 then item.tags = utils.hash_map(item.tags) item.name = utils.extract_name(item) item.types = utils.extract_types(item) item.type = item.types[1] if #item.description ~= 0 then item.summary = match(item.description, patterns.DOCBUNTO_SUMMARY) item.description = item.description:sub(select(2, item.description:find('%s*', #item.summary + 1)) + 1) end item.description = item.description:gsub('%s%s+', '\n\n') new_item_code = true end -- Documentation block reset. if t.type ~= 'comment' or t.data:find('^%-%-%]=*%]$') or t.data:find('^%]=*%]%-%-$') then start_mode = false comment_mode = false doctag_mode = false export_mode = false pragma_mode = false end -- Don't concatenate module return value into item code. if t.data == 'return' and t.posFirst == 1 then return_mode = true end -- Item code concatenation. if item_index ~= 0 and not doctag_mode and not comment_mode and not comment_brace and not return_mode then separator = #item.code ~= 0 and t.posFirst == 1 and '\n' or '' item.code = utils.join{ item.code, separator, t.data } -- Code analysis on item headline. if new_item_code and item.code:find('\n') and t.posFirst == 1 then utils.code_heuristic(item) new_item_code = false end end t, i = tokens[i + 1], i + 1 end documentation.lineno = line_no local package_name = (documentation.tags['alias'] or {}).value or documentation.name or DEFAULT_VARIABLE local package_alias = (documentation.tags['alias'] or {}).value or 'p' local export_ptn = '^%s([.[])' for _, item in ipairs(documentation.items) do if item.name == package_alias or (item.name and item.name:match('^' .. package_alias .. '[.[]')) then item.alias = item.name:gsub(export_ptn:format(package_alias), documentation.name .. '%1') end if item.name == package_name or item.tags['export'] or (item.name and package_name and item.name:find(export_ptn:format(package_name))) or (item.alias and package_name and item.alias:find(export_ptn:format(package_name))) then item.export = true item.tags['local'] = nil item.tags['private'] = nil for index, tag in ipairs(item.tags) do if tag.name == 'local' or tag.name == 'private' then table.remove(item.tags, index) end end end if item.name and (item.name:find('[.:]') or item.name:find('%[[\'"]')) then item.hierarchy = utils.parse_hierarchy(item.name) end item.type = item.type or ((item.alias or item.name or ''):find('[.[]') and 'member' or 'variable') utils.correct_subitem_tag(item) utils.override_item_tag(item, 'name') utils.override_item_tag(item, 'alias') utils.override_item_tag(item, 'summary') utils.override_item_tag(item, 'description') utils.override_item_tag(item, 'class', 'type') end -- Sort documentation items by their access level. -- Items are sorted by whether they are exported at the module level. -- Items are then subsorted by whether the item is `@local` aka `@private`. table.sort(documentation.items, function(item1, item2) local inaccessible1 = item1.tags['local'] or item1.tags['private'] local inaccessible2 = item2.tags['local'] or item2.tags['private'] -- Send package items to the top. if item1.export and not item2.export then return true elseif item2.export and not item1.export then return false -- Send private items to the bottom. elseif inaccessible1 and not inaccessible2 then return false elseif inaccessible2 and not inaccessible1 then return true -- Optional alphabetical sort. elseif options.sort then return (item1.alias or item1.name) < (item2.alias or item2.name) -- Sort via source code order by default. else return item1.lineno < item2.lineno end end) end) if not p_success then mw.addWarning("Docbunto error: " .. p_error) end return documentation end --- Doclet renderer for Docbunto taglet data. -- @function p.doclet -- @param {table} data Taglet documentation data. -- @param[opt] {options} options Configuration options. -- @return {string} Wikitext documentation output. function p.doclet(data, options) local documentation = mw.html.create() local namespace_ptn = utils.join{ '^', mw.site.namespaces[828].name, ':' } local codepage = data.filename:gsub(namespace_ptn, '', 1) options = options or {} options.file = data.filename frame = frame or mw.getCurrentFrame():getParent() or mw.getCurrentFrame() local maybe_md = options.plain and utils.identity or frontend.markdown local tohtml = function(str) return frame:preprocess(maybe_md(str)) end -- Disable edit sections for automatic documentation pages. if not options.code then documentation:wikitext(frame:preprocess('__NOEDITSECTION__')) end -- Information if not options.code and options.box then local custom, infobox = pcall(require, 'Module:Docbunto/infobox') if custom and type(infobox) == 'function' then documentation:wikitext(infobox(data, codepage, frame, options, title, maybe_md)):newline() end end -- Documentation lede. if not options.code and (#(data.summary or '') + #data.description) ~= 0 then local separator = #(data.summary or '') ~= 0 and #data.description ~= 0 and (data.description:find(patterns.DOCBUNTO_WIKITEXT) and '\n\n' or patterns.DOCBUNTO_CONCAT) or '' local intro = utils.join{ data.summary or '', separator, data.description } intro = tohtml(intro:gsub('^(' .. codepage .. ')', mw.text.tag('b', nil, '%1'))) if options.migration then documentation:wikitext(frame:preprocess(mw.text.tag{ name = 'syntaxhighlight', content = intro })):newline():newline() else documentation:wikitext(intro):newline():newline() end end -- Custom documentation preface. if options.preface then documentation:wikitext(options.preface):newline():newline() end -- Start code documentation. local codedoc = mw.html.create() local function_module = data.tags['param'] or data.tags['return'] local header_type = documentation.type == 'classmod' and 'class' or function_module and 'function' or 'items' if (function_module or #data.items ~= 0) and not options.code or options.preface then codedoc :tag('h2') :wikitext(i18n:msg('header-documentation')) :done() :newline() end if (function_module or #data.items ~= 0) then codedoc :tag('h3') :wikitext(i18n:msg('header-' .. header_type)) :done() :newline() end -- Function module support. if function_module then data.type, data.types[1] = 'function', 'function' options.item_id = data.name if not options.code or not options.verbose then data.description = '' end frontend.render_item(codedoc, data, options, frontend.pretask_function_name) if not options.simple and data.tags['param'] then frontend.render_tag(codedoc, 'param', data.tags['param'], options, frontend.pretask_variable_prefix) end if not options.simple and data.tags['error'] then frontend.render_tag(codedoc, 'error', data.tags['error'], options, frontend.pretask_error_line) end if not options.simple and data.tags['return'] then frontend.render_tag(codedoc, 'return', data.tags['return'], options) end end -- Render documentation items. local other_header = false local private_header = false local inaccessible for _, item in ipairs(data.items) do inaccessible = item.tags['local'] or item.tags['private'] if not options.all and inaccessible then break end if not other_header and item.type ~= 'section' and item.type ~= 'type' and not item.export and not item.hierarchy and not inaccessible then codedoc :tag('h3') :wikitext(i18n:msg('header-other')) :done() :newline() other_header = true end if not private_header and options.all and inaccessible then codedoc :tag('h3') :wikitext(i18n:msg('header-private')) :done() :newline() private_header = true end if item.type == 'section' then codedoc :tag('h3') :wikitext((mw.ustring.gsub(item.summary or item.alias or item.name, '[.։。।෴۔።]$', ''))) :done() :newline() if #item.description ~= 0 then codedoc:wikitext(mw.text.trim(item.description)):newline() end elseif item.type == 'type' then if options.strip and item.export and item.hierarchy then if item.alias then item.alias = item.alias:gsub('^[%w_]+[.[]?', '') else item.name = item.name:gsub('^[%w_]+[.[]?', '') end end codedoc :tag('h3') :wikitext(mw.text.tag('code', {}, item.alias or item.name)) :done() :newline() if (#(item.summary or '') + #item.description) ~= 0 then local separator = #(item.summary or '') ~= 0 and #item.description ~= 0 and (item.description:find(patterns.DOCBUNTO_WIKITEXT) and '\n\n' or patterns.DOCBUNTO_CONCAT) or '' codedoc:wikitext((item.summary or '') .. separator .. item.description):newline() end elseif item.type == 'function' then options.item_id = item.alias or item.name frontend.render_item(codedoc, item, options, frontend.pretask_function_name) if not options.simple and item.tags['param'] then frontend.render_tag(codedoc, 'param', item.tags['param'], options, frontend.pretask_variable_prefix) end if not options.simple and item.tags['error'] then frontend.render_tag(codedoc, 'error', item.tags['error'], options, frontend.pretask_error_line) end if not options.simple and item.tags['return'] then frontend.render_tag(codedoc, 'return', item.tags['return'], options) end elseif item.type == 'table' or item.type ~= nil and ( item.type == 'member' or item.type == 'variable' ) and (item.alias or item.name) then options.item_id = item.alias or item.name frontend.render_item(codedoc, item, options) if not options.simple and item.tags['field'] then frontend.render_tag(codedoc, 'field', item.tags['field'], options, frontend.pretask_variable_prefix) end end if item.type ~= 'section' and item.type ~= 'type' then if not options.simple and item.tags['note'] then frontend.render_tag(codedoc, 'note', item.tags['note'], options) end if not options.simple and item.tags['warning'] then frontend.render_tag(codedoc, 'warning', item.tags['warning'], options) end if not options.simple and item.tags['fixme'] then frontend.render_tag(codedoc, 'fixme', item.tags['fixme'], options) end if not options.simple and item.tags['todo'] then frontend.render_tag(codedoc, 'todo', item.tags['todo'], options) hasTodoItems = true end if not options.simple and item.tags['usage'] then frontend.render_tag(codedoc, 'usage', item.tags['usage'], options, frontend.pretask_usage_highlight) end if not options.simple and item.tags['see'] then frontend.render_tag(codedoc, 'see', item.tags['see'], options) end end end -- Render module-level annotations. local header_level = options.code and 'h3' or 'h2' local header_text for _, tag_name in ipairs(p.tags._annotation_hierarchy) do if data.tags[tag_name] then header_text = i18n:msg('tag-' .. tag_name, data.tags[tag_name].value and '1' or '2') header_text = '<' .. header_level .. '>' .. ' ' .. header_text .. ' ' .. '</' .. header_level .. '>' codedoc:newline():wikitext(header_text):newline() if data.tags[tag_name].value then codedoc:wikitext(data.tags[tag_name].value):newline() else for _, tag_el in ipairs(data.tags[tag_name]) do codedoc:wikitext('* ' .. tag_el.value):newline() end end end end -- Byline for autogeneration. if options.footer then codedoc:newline() codedoc:tag('hr'):done() codedoc:newline() codedoc:tag('p'):css( 'text-align', 'right' ):wikitext(i18n:msg('message-autogeneration')) end -- Add nowiki tags for EOF termination in tests. codedoc:tag('nowiki', { selfClosing = true }) -- Code documentation formatting. codedoc = tohtml(tostring(codedoc)) documentation:wikitext(codedoc) -- Endmatter table for module information. if options.card then local endmatter = mw.html.create('table') endmatter:addClass('wikitable') endmatter:css('width', '75%') endmatter:css('margin', '0 auto 1em auto') local module_type = data.type == 'module' and i18n:msg('invocable-module') or i18n:msg('meta-module') endmatter :tag('caption') :wikitext('Module information card (autogenerated)') endmatter :tag('tr') :tag('th'):attr('scope', 'col'):wikitext('Name'):done() :tag('th'):attr('scope', 'col'):wikitext('Value'):done() if options.image or data.info and data.info['image'] then endmatter :tag('tr') :tag('td'):attr('scope', 'row'):wikitext(i18n:msg('endmatter-image')):done() :tag('td') :attr('align', 'center') :wikitext('[[File:' .. (options.image or data.info['image']) .. '|270px|center]]') :done() :done() end if options.caption or data.info and data.info['caption'] then endmatter :tag('tr') :tag('td'):attr('scope', 'row'):wikitext(i18n:msg('endmatter-caption')):done() :tag('td') :attr('align', options.image or data.info['image'] and 'center' or 'left') :wikitext(tohtml(options.caption or data.info['caption'])) :done() :done() end endmatter :tag('tr') :tag('td'):attr('scope', 'row'):wikitext((gsub(i18n:msg('type-variable'), '^%w', mw.ustring.upper))):done() :tag('td') :attr('align', options.image or data.info and data.info['image'] and 'center' or 'left') :wikitext(mw.text.tag({ name = options.image or data.info and data.info['image'] and 'h1' or 'span', content = mw.text.tag({ name = 'b', content = mw.text.tag({ name = 'code', content = data.name or DEFAULT_VARIABLE }) }) })):done() :done() :tag('tr') :tag('td'):wikitext(i18n:msg('code')):done() :tag('td'):wikitext(tohtml('[[' .. data.filename .. ']]')):done() :done() if data.info and data.info['release'] then endmatter :tag('tr') :tag('td'):attr('scope', 'row'):wikitext(i18n:msg('release-status')):done() :tag('td'):wikitext(tohtml(data.info['release'])):done() :done() end if data.summary then endmatter :tag('tr') :tag('td'):attr('scope', 'row'):wikitext(i18n:msg('description')):done() :tag('td'):wikitext(tohtml(data.summary)):done() :done() end if data.info and data.info['author'] then endmatter :tag('tr') :tag('td'):attr('scope', 'row'):wikitext(i18n:msg('author')):done() :tag('td'):newline():wikitext(tohtml(data.info['author'])):done() :done() end if data.info and data.info['attribution'] then endmatter :tag('tr') :tag('td'):attr('scope', 'row'):wikitext(i18n:msg('using-code-by')):done() :tag('td'):wikitext(tohtml(data.info['attribution'])):done() :done() end if data.info and data.info['credit'] then endmatter :tag('tr') :tag('td'):attr('scope', 'row'):wikitext(i18n:msg('other-attribution')):done() :tag('td'):wikitext(tohtml(data.info['credit'])):done() :done() end if data.info and data.info['require'] then data.info['require'] = data.info['require'] :gsub('^[^[%s]+$', '[[%1]]') :gsub('%* ([^[%s]+)', '* [[%1]]') endmatter :tag('tr') :tag('td'):attr('scope', 'row'):wikitext(i18n:msg('dependencies')):done() :tag('td'):newline():wikitext(tohtml(data.info['require'])):done() :done() end if data.info and data.info['demo'] then endmatter :tag('tr') :tag('td'):attr('scope', 'row'):wikitext(i18n:msg('examples')):done() :tag('td'):wikitext(tohtml(data.info['demo'])):done() :done() end endmatter:newline() endmatter = tostring(endmatter) documentation:newline():newline() documentation:wikitext(endmatter) end documentation = tostring(documentation) if hasTodoItems and options.autodoc and mw.title.new('Module:' .. options.file).fullText == mw.title.getCurrentTitle().fullText then documentation = documentation .. '[[Category:' .. i18n:msg('todo') .. ']]' end if mw.title.new('Module:Docbunto/styles.css').exists then documentation = documentation .. frame:extensionTag('templatestyles', '', {src = 'Module:Docbunto/styles.css'}) end return documentation end --- Token dictionary for Docbunto tags. -- Maps Docbunto tag names to tag tokens. -- * Multi-line tags use the `'M'` token. -- * Multi-line preformatted tags use the `'ML'` token. -- * Identifier tags use the `'ID'` token. -- * Single-line tags use the `'S'` token. -- * Flags use the `'N'` token. -- * Type tags use the `'T'` token. -- @table p.tags p.tags = { -- Item-level tags, available for global use. ['param'] = 'M', ['see'] = 'M', ['note'] = 'M', ['usage'] = 'ML', ['description'] = 'M', ['field'] = 'M', ['return'] = 'M', ['fixme'] = 'M', ['todo'] = 'M', ['warning'] = 'M', ['error'] = 'M'; ['class'] = 'ID', ['name'] = 'ID', ['alias'] = 'ID'; ['summary'] = 'S', ['pragma'] = 'S', ['factory'] = 'S', ['release'] = 'S', ['author'] = 'S', ['copyright'] = 'S', ['license'] = 'S', ['image'] = 'S', ['caption'] = 'S', ['require'] = 'S', ['attribution'] = 'S', ['credit'] = 'S', ['demo'] = 'S'; ['local'] = 'N', ['export'] = 'N', ['private'] = 'N', ['constructor'] = 'N', ['static'] = 'N'; -- Project-level tags, all scoped to a file. ['module'] = 'T', ['script'] = 'T', ['classmod'] = 'T', ['topic'] = 'T', ['submodule'] = 'T', ['example'] = 'T', ['file'] = 'T'; -- Module-level tags, used to register module items. ['function'] = 'T', ['table'] = 'T', ['member'] = 'T', ['variable'] = 'T', ['section'] = 'T', ['type'] = 'T'; } p.tags._alias = { -- Normal aliases. ['about'] = 'summary', ['abstract'] = 'summary', ['brief'] = 'summary', ['bug'] = 'fixme', ['argument'] = 'param', ['credits'] = 'credit', ['code'] = 'usage', ['details'] = 'description', ['discussion'] = 'description', ['exception'] = 'error', ['lfunction'] = 'function', ['package'] = 'module', ['property'] = 'member', ['raise'] = 'error', ['requires'] = 'require', ['returns'] = 'return', ['throws'] = 'error', ['typedef'] = 'type', -- Typed aliases. ['bool'] = 'field', ['func'] = 'field', ['int'] = 'field', ['number'] = 'field', ['string'] = 'field', ['tab'] = 'field', ['vararg'] = 'param', ['tfield'] = 'field', ['tparam'] = 'param', ['treturn'] = 'return' } p.tags._type_alias = { -- Implicit type value alias. ['bool'] = 'boolean', ['func'] = 'function', ['int'] = 'number', ['number'] = 'number', ['string'] = 'string', ['tab'] = 'table', ['vararg'] = '...', -- Pure typed modifier alias. ['tfield'] = 'variable', ['tparam'] = 'variable', ['treturn'] = 'variable' } p.tags._project_level = { -- Contains code. ['module'] = true, ['script'] = true, ['classmod'] = true, ['submodule'] = true, ['file'] = true, -- Contains documentation. ['topic'] = true, ['example'] = true } p.tags._code_types = { ['module'] = true, ['script'] = true, ['classmod'] = true } p.tags._module_info = { ['image'] = true, ['caption'] = true, ['release'] = true, ['author'] = true, ['copyright'] = true, ['license'] = true, ['require'] = true, ['credit'] = true, ['attribution'] = true, ['demo'] = true } p.tags._annotation_tags = { ['field'] = true, ['warning'] = true, ['fixme'] = true, ['note'] = true, ['todo'] = true, ['see'] = true } p.tags._annotation_hierarchy = { 'warning', 'fixme', 'note', 'todo', 'see' } p.tags._privacy_tags = { ['private'] = true, ['local'] = true } p.tags._generic_tags = { ['variable'] = true, ['member'] = true } p.tags._subtype_tags = { ['factory'] = true, ['local'] = true, ['private'] = true, ['constructor'] = true, ['static'] = true } p.tags._subtype_hierarchy = { 'private', 'local', 'static', 'factory', 'constructor' } return p 97e0wti9mg3650r9kw1xqydjisw6dtc Module:Docbunto/references 828 101352 803404 2024-09-25T06:37:59Z en>Awesome Aasim 0 copy from testwiki, based on work from [[wikia:w:c:dev:Module:Docbunto/references]] 803404 Scribunto text/plain --- Reference table for Docbunto reference links. -- List of global functions and tables available in Scribunto. -- @file docbunto_references --- Reference data map for types. -- @property {table} docbunto_references.types --- Reference link map for global items. -- @property {table} docbunto_references.items return { types = { -- Lua types available to Scribunto. ["nil"] = { link = "mw:Extension:Scribunto/Lua reference manual#nil", name = "nil" }, ["bool"] = { link = "mw:Extension:Scribunto/Lua reference manual#boolean", name = "boolean" }, ["boolean"] = { link = "mw:Extension:Scribunto/Lua reference manual#boolean", name = "boolean" }, ["string"] = { link = "mw:Extension:Scribunto/Lua reference manual#string", name = "string" }, ["int"] = { link = "mw:Extension:Scribunto/Lua reference manual#number", name = "number" }, ["number"] = { link = "mw:Extension:Scribunto/Lua reference manual#number", name = "number" }, ["tab"] = { link = "mw:Extension:Scribunto/Lua reference manual#table", name = "table" }, ["table"] = { link = "mw:Extension:Scribunto/Lua reference manual#table", name = "table" }, ["sequence"] = { link = "mw:Extension:Scribunto/Lua reference manual#sequence", name = "sequence" }, ["func"] = { link = "mw:Extension:Scribunto/Lua reference manual#function", name = "function" }, ["function"] = { link = "mw:Extension:Scribunto/Lua reference manual#function", name = "function" }, -- Scribunto library objects. ["mw.loadData"] = { link = "mw:Extension:Scribunto/Lua reference manual#mw.loadData", name = "DataWrapper" }, ["DataWrapper"] = { link = "mw:Extension:Scribunto/Lua reference manual#mw.loadData", name = "DataWrapper" }, ["Frame"] = { link = "mw:Extension:Scribunto/Lua reference manual#Frame object", name = "Frame" }, ["mw.language"] = { link = "mw:Extension:Scribunto/Lua reference manual#Language object", name = "Lang" }, ["Lang"] = { link = "mw:Extension:Scribunto/Lua reference manual#Language object", name = "Lang" }, ["mw.uri"] = { link = "mw:Extension:Scribunto/Lua reference manual#URI object", name = "Uri" }, ["Uri"] = { link = "mw:Extension:Scribunto/Lua reference manual#URI object", name = "Uri" }, ["mw.html"] = { link = "mw:Extension:Scribunto/Lua reference manual#HTML object", name = "HTMLBuilder" }, ["HTMLBuilder"] = { link = "mw:Extension:Scribunto/Lua reference manual#HTML object", name = "HTMLBuilder" }, ["mw.title"] = { link = "mw:Extension:Scribunto/Lua reference manual#Title objects", name = "Title" }, ["Title"] = { link = "mw:Extension:Scribunto/Lua reference manual#Title objects", name = "Title" }, ["mw.message"] = { link = "mw:Extension:Scribunto/Lua reference manual#Message object", name = "Msg" }, ["Msg"] = { link = "mw:Extension:Scribunto/Lua reference manual#Message object", name = "Msg" }, }, items = { -- Types and primitives. ["..."] = "mw:Extension:Scribunto/Lua reference manual#varargs", ["nil"] = "mw:Extension:Scribunto/Lua reference manual#nil", ["boolean"] = "mw:Extension:Scribunto/Lua reference manual#boolean", ["number"] = "mw:Extension:Scribunto/Lua reference manual#number", ["sequence"] = "mw:Extension:Scribunto/Lua reference manual#sequence", ["function"] = "mw:Extension:Scribunto/Lua reference manual#function", -- Global variables and functions. ["_G"] = "mw:Extension:Scribunto/Lua reference manual#_G", ["_VERSION"] = "mw:Extension:Scribunto/Lua reference manual#_VERSION", ["assert"] = "mw:Extension:Scribunto/Lua reference manual#assert", ["error"] = "mw:Extension:Scribunto/Lua reference manual#error", ["getmetatable"] = "mw:Extension:Scribunto/Lua reference manual#getmetatable", ["setmetatable"] = "mw:Extension:Scribunto/Lua reference manual#setmetatable", ["ipairs"] = "mw:Extension:Scribunto/Lua reference manual#ipairs", ["next"] = "mw:Extension:Scribunto/Lua reference manual#next", ["pairs"] = "mw:Extension:Scribunto/Lua reference manual#pairs", ["pcall"] = "mw:Extension:Scribunto/Lua reference manual#pcall", ["xpcall"] = "mw:Extension:Scribunto/Lua reference manual#xpcall", ["rawequal"] = "mw:Extension:Scribunto/Lua reference manual#rawequal", ["rawget"] = "mw:Extension:Scribunto/Lua reference manual#rawget", ["rawset"] = "mw:Extension:Scribunto/Lua reference manual#rawset", ["require"] = "mw:Extension:Scribunto/Lua reference manual#require", ["select"] = "mw:Extension:Scribunto/Lua reference manual#select", ["tonumber"] = "mw:Extension:Scribunto/Lua reference manual#tonumber", ["tostring"] = "mw:Extension:Scribunto/Lua reference manual#tostring", ["type"] = "mw:Extension:Scribunto/Lua reference manual#type", ["unpack"] = "mw:Extension:Scribunto/Lua reference manual#unpack", -- Lua debug library. ["debug"] = "mw:Extension:Scribunto/Lua reference manual#Debug library", ["debug.traceback"] = "mw:Extension:Scribunto/Lua reference manual#debug.traceback", -- Lua math library. ["math"] = "mw:Extension:Scribunto/Lua reference manual#Math library", ["math.abs"] = "mw:Extension:Scribunto/Lua reference manual#math.abs", ["math.acos"] = "mw:Extension:Scribunto/Lua reference manual#math.acos", ["math.asin"] = "mw:Extension:Scribunto/Lua reference manual#math.asin", ["math.atan"] = "mw:Extension:Scribunto/Lua reference manual#math.atan", ["math.atan2"] = "mw:Extension:Scribunto/Lua reference manual#math.atan2", ["math.ceil"] = "mw:Extension:Scribunto/Lua reference manual#math.ceil", ["math.cos"] = "mw:Extension:Scribunto/Lua reference manual#math.cos", ["math.cosh"] = "mw:Extension:Scribunto/Lua reference manual#math.cosh", ["math.deg"] = "mw:Extension:Scribunto/Lua reference manual#math.deg", ["math.exp"] = "mw:Extension:Scribunto/Lua reference manual#math.exp", ["math.floor"] = "mw:Extension:Scribunto/Lua reference manual#math.floor", ["math.fmod"] = "mw:Extension:Scribunto/Lua reference manual#math.fmod", ["math.frexp"] = "mw:Extension:Scribunto/Lua reference manual#math.frexp", ["math.huge"] = "mw:Extension:Scribunto/Lua reference manual#math.huge", ["math.ldexp"] = "mw:Extension:Scribunto/Lua reference manual#math.ldexp", ["math.log"] = "mw:Extension:Scribunto/Lua reference manual#math.log", ["math.log10"] = "mw:Extension:Scribunto/Lua reference manual#math.log10", ["math.max"] = "mw:Extension:Scribunto/Lua reference manual#math.max", ["math.min"] = "mw:Extension:Scribunto/Lua reference manual#math.min", ["math.modf"] = "mw:Extension:Scribunto/Lua reference manual#math.modf", ["math.pi"] = "mw:Extension:Scribunto/Lua reference manual#math.pi", ["math.pow"] = "mw:Extension:Scribunto/Lua reference manual#math.pow", ["math.rad"] = "mw:Extension:Scribunto/Lua reference manual#math.rad", ["math.random"] = "mw:Extension:Scribunto/Lua reference manual#math.random", ["math.randomseed"] = "mw:Extension:Scribunto/Lua reference manual#math.randomseed", ["math.sin"] = "mw:Extension:Scribunto/Lua reference manual#math.sin", ["math.sinh"] = "mw:Extension:Scribunto/Lua reference manual#math.sinh", ["math.sqrt"] = "mw:Extension:Scribunto/Lua reference manual#math.sqrt", ["math.tan"] = "mw:Extension:Scribunto/Lua reference manual#math.tan", ["math.tanh"] = "mw:Extension:Scribunto/Lua reference manual#math.tanh", -- Operating system library. ["os"] = "mw:Extension:Scribunto/Lua reference manual#Operating system library", ["os.clock"] = "mw:Extension:Scribunto/Lua reference manual#os.clock", ["os.date"] = "mw:Extension:Scribunto/Lua reference manual#os.date", ["os.difftime"] = "mw:Extension:Scribunto/Lua reference manual#os.difftime", ["os.time"] = "mw:Extension:Scribunto/Lua reference manual#os.time", -- Package library. ["package"] = "mw:Extension:Scribunto/Lua reference manual#Package library", ["package.loaded"] = "mw:Extension:Scribunto/Lua reference manual#package.loaded", ["package.loaders"] = "mw:Extension:Scribunto/Lua reference manual#package.loaders", ["package.preload"] = "mw:Extension:Scribunto/Lua reference manual#package.preload", ["package.seeall"] = "mw:Extension:Scribunto/Lua reference manual#package.seeall", -- Lua string library. ["string"] = "mw:Extension:Scribunto/Lua reference manual#String library", ["string.byte"] = "mw:Extension:Scribunto/Lua reference manual#string.byte", ["string.char"] = "mw:Extension:Scribunto/Lua reference manual#string.char", ["string.find"] = "mw:Extension:Scribunto/Lua reference manual#string.find", ["string.format"] = "mw:Extension:Scribunto/Lua reference manual#string.format", ["string.gmatch"] = "mw:Extension:Scribunto/Lua reference manual#string.gmatch", ["string.gsub"] = "mw:Extension:Scribunto/Lua reference manual#string.gsub", ["string.len"] = "mw:Extension:Scribunto/Lua reference manual#string.len", ["string.lower"] = "mw:Extension:Scribunto/Lua reference manual#string.lower", ["string.match"] = "mw:Extension:Scribunto/Lua reference manual#string.match", ["string.rep"] = "mw:Extension:Scribunto/Lua reference manual#string.rep", ["string.reverse"] = "mw:Extension:Scribunto/Lua reference manual#string.reverse", ["string.sub"] = "mw:Extension:Scribunto/Lua reference manual#string.sub", ["string.upper"] = "mw:Extension:Scribunto/Lua reference manual#string.upper", -- Lua table library. ["table"] = "mw:Extension:Scribunto/Lua reference manual#Table library", ["table.concat"] = "mw:Extension:Scribunto/Lua reference manual#table.concat", ["table.insert"] = "mw:Extension:Scribunto/Lua reference manual#table.insert", ["table.maxn"] = "mw:Extension:Scribunto/Lua reference manual#table.maxn", ["table.remove"] = "mw:Extension:Scribunto/Lua reference manual#table.remove", ["table.sort"] = "mw:Extension:Scribunto/Lua reference manual#table.sort", -- MediaWiki Scribunto library. ["mw"] = "mw:Extension:Scribunto/Lua reference manual", ["mw.allToString"] = "mw:Extension:Scribunto/Lua reference manual#mw.allToString", ["mw.clearLogBuffer"] = "mw:Extension:Scribunto/Lua reference manual#mw.clearLogBuffer", ["mw.clone"] = "mw:Extension:Scribunto/Lua reference manual#mw.clone", ["mw.executeFunction"] = "mw:Extension:Scribunto/Lua reference manual#mw.executeFunction", ["mw.executeModule"] = "mw:Extension:Scribunto/Lua reference manual#mw.executeModule", ["mw.getCurrentFrame"] = "mw:Extension:Scribunto/Lua reference manual#mw.getCurrentFrame", ["mw.getLogBuffer"] = "mw:Extension:Scribunto/Lua reference manual#mw.getLogBuffer", ["mw.incrementExpensiveFunctionCount"] = "mw:Extension:Scribunto/Lua reference manual#mw.incrementExpensiveFunctionCount", ["mw.loadData"] = "mw:Extension:Scribunto/Lua reference manual#mw.loadData", ["DataWrapper"] = "mw:Extension:Scribunto/Lua reference manual#mw.loadData", ["mw.log"] = "mw:Extension:Scribunto/Lua reference manual#mw.log", ["print"] = "mw:Extension:Scribunto/Lua reference manual#mw.log", -- Scribunto frame object. ["frame"] = "mw:Extension:Scribunto/Lua reference manual#Frame object", ["frame.args"] = "mw:Extension:Scribunto/Lua reference manual#frame.args", ["frame:getParent"] = "mw:Extension:Scribunto/Lua reference manual#frame:getParent", ["frame:expandTemplate"] = "mw:Extension:Scribunto/Lua reference manual#frame:expandTemplate", ["frame:preprocess"] = "mw:Extension:Scribunto/Lua reference manual#frame:preprocess", ["frame:getArgument"] = "mw:Extension:Scribunto/Lua reference manual#frame:getArgument", ["frame:newParserValue"] = "mw:Extension:Scribunto/Lua reference manual#frame:newParserValue", ["frame:newTemplateParserValue"] = "mw:Extension:Scribunto/Lua reference manual#frame:newTemplateParserValue", ["frame:argumentPairs"] = "mw:Extension:Scribunto/Lua reference manual#frame:argumentPairs", ["frame:getTitle"] = "mw:Extension:Scribunto/Lua reference manual#frame:getTitle", -- Scribunto frame object as `Frame` class. ["Frame"] = "mw:Extension:Scribunto/Lua reference manual#Frame object", ["Frame.args"] = "mw:Extension:Scribunto/Lua reference manual#frame.args", ["Frame:getParent"] = "mw:Extension:Scribunto/Lua reference manual#frame:getParent", ["Frame:expandTemplate"] = "mw:Extension:Scribunto/Lua reference manual#frame:expandTemplate", ["Frame:preprocess"] = "mw:Extension:Scribunto/Lua reference manual#frame:preprocess", ["Frame:getArgument"] = "mw:Extension:Scribunto/Lua reference manual#frame:getArgument", ["Frame:newParserValue"] = "mw:Extension:Scribunto/Lua reference manual#frame:newParserValue", ["Frame:newTemplateParserValue"] = "mw:Extension:Scribunto/Lua reference manual#frame:newTemplateParserValue", ["Frame:argumentPairs"] = "mw:Extension:Scribunto/Lua reference manual#frame:argumentPairs", ["Frame:getTitle"] = "mw:Extension:Scribunto/Lua reference manual#frame:getTitle", -- Scribunto language library. ["mw.language"] = "mw:Extension:Scribunto/Lua reference manual#Language library", ["mw.language.fetchLanguageName"] = "mw:Extension:Scribunto/Lua reference manual#mw.language.fetchLanguageName", ["mw.language.getContentLanguage"] = "mw:Extension:Scribunto/Lua reference manual#mw.language.getContentLanguage", ["mw.language.isValidBuiltInCode"] = "mw:Extension:Scribunto/Lua reference manual#mw.language.isValidBuiltInCode", ["mw.language.isValidCode"] = "mw:Extension:Scribunto/Lua reference manual#mw.language.isValidCode", ["mw.language.new"] = "mw:Extension:Scribunto/Lua reference manual#mw.language.new", -- Scribunto language object. ["lang"] = "mw:Extension:Scribunto/Lua reference manual#Language object", ["mw.language:getCode"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:getCode", ["mw.language:isRTL"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:isRTL", ["mw.language:lc"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:lc", ["mw.language:lcfirst"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:lcfirst", ["mw.language:uc"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:uc", ["mw.language:ucfirst"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:ucfirst", ["mw.language:caseFold"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:caseFold", ["mw.language:formatNum"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:formatNum", ["mw.language:formatDate"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:formatDate", ["mw.language:parseFormattedNumber"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:parseFormattedNumber", ["mw.language:convertPlural"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:convertPlural", ["mw.language:convertGrammar"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:convertGrammar", ["mw.language:gender"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:gender", -- Scribunto language object as `Lang` class. ["Lang"] = "mw:Extension:Scribunto/Lua reference manual#Language object", ["Lang:getCode"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:getCode", ["Lang:isRTL"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:isRTL", ["Lang:lc"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:lc", ["Lang:lcfirst"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:lcfirst", ["Lang:uc"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:uc", ["Lang:ucfirst"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:ucfirst", ["Lang:caseFold"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:caseFold", ["Lang:formatNum"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:formatNum", ["Lang:formatDate"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:formatDate", ["Lang:parseFormattedNumber"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:parseFormattedNumber", ["Lang:convertPlural"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:convertPlural", ["Lang:convertGrammar"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:convertGrammar", ["Lang:gender"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:gender", -- Scribunto site library. ["mw.site"] = "mw:Extension:Scribunto/Lua reference manual#Site library", ["mw.site.currentVersion"] = "mw:Extension:Scribunto/Lua reference manual#mw.site.currentVersion", ["mw.site.scriptPath"] = "mw:Extension:Scribunto/Lua reference manual#mw.site.scriptPath", ["mw.site.server"] = "mw:Extension:Scribunto/Lua reference manual#mw.site.server", ["mw.site.siteName"] = "mw:Extension:Scribunto/Lua reference manual#mw.site.siteName", ["mw.site.stylePath"] = "mw:Extension:Scribunto/Lua reference manual#mw.site.stylePath", ["mw.site.namespaces"] = "mw:Extension:Scribunto/Lua reference manual#mw.site.namespaces", ["mw.site.contentNamespaces"] = "mw:Extension:Scribunto/Lua reference manual#mw.site.contentNamespaces", ["mw.site.subjectNamespaces"] = "mw:Extension:Scribunto/Lua reference manual#mw.site.subjectNamespaces", ["mw.site.talkNamespaces"] = "mw:Extension:Scribunto/Lua reference manual#mw.site.talkNamespaces", ["mw.site.sassParams"] = "mw:Extension:Scribunto/Lua reference manual#mw.site.sassParams", ["mw.site.stats"] = "mw:Extension:Scribunto/Lua reference manual#mw.site.stats", ["mw.site.stats.pagesInCategory"] = "mw:Extension:Scribunto/Lua reference manual#mw.site.stats.pagesInCategory", ["mw.site.stats.pagesInNamespace"] = "mw:Extension:Scribunto/Lua reference manual#mw.site.stats.pagesInNamespace", ["mw.site.stats.usersInGroup"] = "mw:Extension:Scribunto/Lua reference manual#mw.site.stats.usersInGroup", -- Scribunto URI library. ["mw.uri"] = "mw:Extension:Scribunto/Lua reference manual#Uri library", ["mw.uri.encode"] = "mw:Extension:Scribunto/Lua reference manual#mw.uri.encode", ["mw.uri.decode"] = "mw:Extension:Scribunto/Lua reference manual#mw.uri.decode", ["mw.uri.anchorEncode"] = "mw:Extension:Scribunto/Lua reference manual#mw.uri.anchorEncode", ["mw.uri.buildQueryString"] = "mw:Extension:Scribunto/Lua reference manual#mw.uri.buildQueryString", ["mw.uri.parseQueryString"] = "mw:Extension:Scribunto/Lua reference manual#mw.uri.parseQueryString", ["mw.uri.canonicalUrl"] = "mw:Extension:Scribunto/Lua reference manual#mw.uri.canonicalUrl", ["mw.uri.fullUrl"] = "mw:Extension:Scribunto/Lua reference manual#mw.uri.fullUrl", ["mw.uri.localUrl"] = "mw:Extension:Scribunto/Lua reference manual#mw.uri.localUrl", ["mw.uri.new"] = "mw:Extension:Scribunto/Lua reference manual#mw.uri.new", ["mw.uri.validate"] = "mw:Extension:Scribunto/Lua reference manual#mw.uri.validate", -- Scribunto URI object. ["uri"] = "mw:Extension:Scribunto/Lua reference manual#URI object", ["mw.uri:parse"] = "mw:Extension:Scribunto/Lua reference manual#mw.uri:parse", ["mw.uri:clone"] = "mw:Extension:Scribunto/Lua reference manual#mw.uri:clone", ["mw.uri:extend"] = "mw:Extension:Scribunto/Lua reference manual#mw.uri:extend", -- Scribunto URI object as `Uri` class. ["Uri"] = "mw:Extension:Scribunto/Lua reference manual#URI object", ["Uri:parse"] = "mw:Extension:Scribunto/Lua reference manual#mw.uri:parse", ["Uri:parse"] = "mw:Extension:Scribunto/Lua reference manual#mw.uri:parse", ["Uri:clone"] = "mw:Extension:Scribunto/Lua reference manual#mw.uri:clone", ["Uri:extend"] = "mw:Extension:Scribunto/Lua reference manual#mw.uri:extend", -- Scribunto UTF8-safe string library. ["mw.ustring"] = "mw:Extension:Scribunto/Lua reference manual#Ustring library", ["mw.ustring.maxPatternLength"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.maxPatternLength", ["mw.ustring.maxStringLength"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.maxStringLength", ["mw.ustring.byte"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.byte", ["mw.ustring.byteoffset"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.byteoffset", ["mw.ustring.char"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.char", ["mw.ustring.codepoint"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.codepoint", ["mw.ustring.find"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.find", ["mw.ustring.format"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.format", ["mw.ustring.gcodepoint"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.gcodepoint", ["mw.ustring.gmatch"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.gmatch", ["mw.ustring.gsub"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.gsub", ["mw.ustring.isutf8"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.isutf8", ["mw.ustring.len"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.len", ["mw.ustring.lower"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.lower", ["mw.ustring.match"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.match", ["mw.ustring.rep"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.rep", ["mw.ustring.sub"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.sub", ["mw.ustring.toNFC"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.toNFC", ["mw.ustring.toNFD"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.toNFD", ["mw.ustring.upper"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.upper", -- Scribunto HTML library. ["mw.html"] = "mw:Extension:Scribunto/Lua reference manual#HTML library", ["mw.html.create"] = "mw:Extension:Scribunto/Lua reference manual#mw.html.create", -- Scribunto HTML object. ["html"] = "mw:Extension:Scribunto/Lua reference manual#HTML object", ["mw.html:node"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:node", ["mw.html:wikitext"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:wikitext", ["mw.html:newline"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:newline", ["mw.html:tag"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:tag", ["mw.html:attr"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:attr", ["mw.html:getAttr"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:getAttr", ["mw.html:addClass"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:addClass", ["mw.html:css"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:css", ["mw.html:cssText"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:cssText", ["mw.html:done"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:done", ["mw.html:allDone"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:allDone", -- Scribunto HTML object as `HTMLBuilder` class. ["HTMLBuilder"] = "mw:Extension:Scribunto/Lua reference manual#HTML object", ["HTMLBuilder:node"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:node", ["HTMLBuilder:wikitext"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:wikitext", ["HTMLBuilder:newline"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:newline", ["HTMLBuilder:tag"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:tag", ["HTMLBuilder:attr"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:attr", ["HTMLBuilder:getAttr"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:getAttr", ["HTMLBuilder:addClass"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:addClass", ["HTMLBuilder:css"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:css", ["HTMLBuilder:cssText"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:cssText", ["HTMLBuilder:done"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:done", ["HTMLBuilder:allDone"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:allDone", -- Scribunto text library. ["mw.text"] = "mw:Extension:Scribunto/Lua reference manual#Text library", ["mw.text.decode"] = "mw:Extension:Scribunto/Lua reference manual#mw.text.decode", ["mw.text.encode"] = "mw:Extension:Scribunto/Lua reference manual#mw.text.encode", ["mw.text.listToText"] = "mw:Extension:Scribunto/Lua reference manual#mw.text.listToText", ["mw.text.nowiki"] = "mw:Extension:Scribunto/Lua reference manual#mw.text.nowiki", ["mw.text.split"] = "mw:Extension:Scribunto/Lua reference manual#mw.text.split", ["mw.text.gsplit"] = "mw:Extension:Scribunto/Lua reference manual#mw.text.gsplit", ["mw.text.tag"] = "mw:Extension:Scribunto/Lua reference manual#mw.text.tag", ["mw.text.trim"] = "mw:Extension:Scribunto/Lua reference manual#mw.text.trim", ["mw.text.truncate"] = "mw:Extension:Scribunto/Lua reference manual#mw.text.truncate", ["mw.text.unstrip"] = "mw:Extension:Scribunto/Lua reference manual#mw.text.unstrip", -- Scribunto title library. ["mw.title"] = "mw:Extension:Scribunto/Lua reference manual#Title library", ["mw.title.equals"] = "mw:Extension:Scribunto/Lua reference manual#mw.title.equals", ["mw.title.compare"] = "mw:Extension:Scribunto/Lua reference manual#mw.title.compare", ["mw.title.getCurrentTitle"] = "mw:Extension:Scribunto/Lua reference manual#mw.title.getCurrentTile", ["mw.title.new"] = "mw:Extension:Scribunto/Lua reference manual#mw.title.new", ["mw.title.makeTitle"] = "mw:Extension:Scribunto/Lua reference manual#mw.title.makeTitle", -- Scribunto title object. ["mw.title.id"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.interwiki"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.namespace"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.fragment"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.nsText"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.subjectNsText"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.text"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.prefixedText"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.fullText"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.rootText"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.baseText"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.subpageText"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.canTalk"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.exists"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.fileExists"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.isContentPage"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.isExternal"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.isLocal"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.isRedirect"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.isSpecialPage"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.isSubpage"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.isTalkPage"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title:isSubpageOf"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title:inNamespace"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title:inNamespaces"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title:hasSubjectNamespace"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.basePageTitle"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.rootPageTitle"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.talkPageTitle"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.subjectPageTitle"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.protectionLevels"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title:subPageTitle"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title:partialUrl"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title:fullUrl"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title:localUrl"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title:canonicalUrl"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title:getContent"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", -- Scribunto title object as `Title` class. ["Title"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.id"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.interwiki"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.namespace"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.fragment"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.nsText"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.subjectNsText"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.text"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.prefixedText"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.fullText"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.rootText"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.baseText"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.subpageText"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.canTalk"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.exists"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.fileExists"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.isContentPage"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.isExternal"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.isLocal"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.isRedirect"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.isSpecialPage"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.isSubpage"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.isTalkPage"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title:isSubpageOf"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title:inNamespace"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title:inNamespaces"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title:hasSubjectNamespace"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.basePageTitle"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.rootPageTitle"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.talkPageTitle"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.subjectPageTitle"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.protectionLevels"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title:subPageTitle"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title:partialUrl"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title:fullUrl"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title:localUrl"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title:canonicalUrl"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title:getContent"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", -- Scribunto message library. ["mw.message"] = "mw:Extension:Scribunto/Lua reference manual#Message library", ["mw.message.new"] = "mw:Extension:Scribunto/Lua reference manual#mw.message.new", ["mw.message.newFallbackSequence"] = "mw:Extension:Scribunto/Lua reference manual#mw.message.newFallbackSequence", ["mw.message.rawParam"] = "mw:Extension:Scribunto/Lua reference manual#mw.message.rawParam", ["mw.message.numParam"] = "mw:Extension:Scribunto/Lua reference manual#mw.message.numParam", ["mw.message.getDefaultLanguage"] = "mw:Extension:Scribunto/Lua reference manual#mw.message.getDefaultLanguage", -- Scribunto message object. ["msg"] = "mw:Extension:Scribunto/Lua reference manual#Message object", ["mw.message:params"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:params", ["mw.message:rawParams"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:rawParams", ["mw.message:numParams"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:numParams", ["mw.message:inLanguage"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:inLanguage", ["mw.message:useDatabase"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:useDatabase", ["mw.message:plain"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:plain", ["mw.message:exists"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:exists", ["mw.message:isBlank"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:isBlank", ["mw.message:isDisabled"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:isDisabled", -- Scribunto message object as `Msg` class. ["Msg"] = "mw:Extension:Scribunto/Lua reference manual#Message object", ["Msg:params"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:params", ["Msg:rawParams"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:rawParams", ["Msg:numParams"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:numParams", ["Msg:inLanguage"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:inLanguage", ["Msg:useDatabase"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:useDatabase", ["Msg:plain"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:plain", ["Msg:exists"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:exists", ["Msg:isBlank"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:isBlank", ["Msg:isDisabled"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:isDisabled", } } mjxb8skj02djmabwzn0kbg2eptjb0r6 803405 803404 2026-07-29T23:41:28Z SM7 3953 1 revision imported from [[:en:Module:Docbunto/references]] 803404 Scribunto text/plain --- Reference table for Docbunto reference links. -- List of global functions and tables available in Scribunto. -- @file docbunto_references --- Reference data map for types. -- @property {table} docbunto_references.types --- Reference link map for global items. -- @property {table} docbunto_references.items return { types = { -- Lua types available to Scribunto. ["nil"] = { link = "mw:Extension:Scribunto/Lua reference manual#nil", name = "nil" }, ["bool"] = { link = "mw:Extension:Scribunto/Lua reference manual#boolean", name = "boolean" }, ["boolean"] = { link = "mw:Extension:Scribunto/Lua reference manual#boolean", name = "boolean" }, ["string"] = { link = "mw:Extension:Scribunto/Lua reference manual#string", name = "string" }, ["int"] = { link = "mw:Extension:Scribunto/Lua reference manual#number", name = "number" }, ["number"] = { link = "mw:Extension:Scribunto/Lua reference manual#number", name = "number" }, ["tab"] = { link = "mw:Extension:Scribunto/Lua reference manual#table", name = "table" }, ["table"] = { link = "mw:Extension:Scribunto/Lua reference manual#table", name = "table" }, ["sequence"] = { link = "mw:Extension:Scribunto/Lua reference manual#sequence", name = "sequence" }, ["func"] = { link = "mw:Extension:Scribunto/Lua reference manual#function", name = "function" }, ["function"] = { link = "mw:Extension:Scribunto/Lua reference manual#function", name = "function" }, -- Scribunto library objects. ["mw.loadData"] = { link = "mw:Extension:Scribunto/Lua reference manual#mw.loadData", name = "DataWrapper" }, ["DataWrapper"] = { link = "mw:Extension:Scribunto/Lua reference manual#mw.loadData", name = "DataWrapper" }, ["Frame"] = { link = "mw:Extension:Scribunto/Lua reference manual#Frame object", name = "Frame" }, ["mw.language"] = { link = "mw:Extension:Scribunto/Lua reference manual#Language object", name = "Lang" }, ["Lang"] = { link = "mw:Extension:Scribunto/Lua reference manual#Language object", name = "Lang" }, ["mw.uri"] = { link = "mw:Extension:Scribunto/Lua reference manual#URI object", name = "Uri" }, ["Uri"] = { link = "mw:Extension:Scribunto/Lua reference manual#URI object", name = "Uri" }, ["mw.html"] = { link = "mw:Extension:Scribunto/Lua reference manual#HTML object", name = "HTMLBuilder" }, ["HTMLBuilder"] = { link = "mw:Extension:Scribunto/Lua reference manual#HTML object", name = "HTMLBuilder" }, ["mw.title"] = { link = "mw:Extension:Scribunto/Lua reference manual#Title objects", name = "Title" }, ["Title"] = { link = "mw:Extension:Scribunto/Lua reference manual#Title objects", name = "Title" }, ["mw.message"] = { link = "mw:Extension:Scribunto/Lua reference manual#Message object", name = "Msg" }, ["Msg"] = { link = "mw:Extension:Scribunto/Lua reference manual#Message object", name = "Msg" }, }, items = { -- Types and primitives. ["..."] = "mw:Extension:Scribunto/Lua reference manual#varargs", ["nil"] = "mw:Extension:Scribunto/Lua reference manual#nil", ["boolean"] = "mw:Extension:Scribunto/Lua reference manual#boolean", ["number"] = "mw:Extension:Scribunto/Lua reference manual#number", ["sequence"] = "mw:Extension:Scribunto/Lua reference manual#sequence", ["function"] = "mw:Extension:Scribunto/Lua reference manual#function", -- Global variables and functions. ["_G"] = "mw:Extension:Scribunto/Lua reference manual#_G", ["_VERSION"] = "mw:Extension:Scribunto/Lua reference manual#_VERSION", ["assert"] = "mw:Extension:Scribunto/Lua reference manual#assert", ["error"] = "mw:Extension:Scribunto/Lua reference manual#error", ["getmetatable"] = "mw:Extension:Scribunto/Lua reference manual#getmetatable", ["setmetatable"] = "mw:Extension:Scribunto/Lua reference manual#setmetatable", ["ipairs"] = "mw:Extension:Scribunto/Lua reference manual#ipairs", ["next"] = "mw:Extension:Scribunto/Lua reference manual#next", ["pairs"] = "mw:Extension:Scribunto/Lua reference manual#pairs", ["pcall"] = "mw:Extension:Scribunto/Lua reference manual#pcall", ["xpcall"] = "mw:Extension:Scribunto/Lua reference manual#xpcall", ["rawequal"] = "mw:Extension:Scribunto/Lua reference manual#rawequal", ["rawget"] = "mw:Extension:Scribunto/Lua reference manual#rawget", ["rawset"] = "mw:Extension:Scribunto/Lua reference manual#rawset", ["require"] = "mw:Extension:Scribunto/Lua reference manual#require", ["select"] = "mw:Extension:Scribunto/Lua reference manual#select", ["tonumber"] = "mw:Extension:Scribunto/Lua reference manual#tonumber", ["tostring"] = "mw:Extension:Scribunto/Lua reference manual#tostring", ["type"] = "mw:Extension:Scribunto/Lua reference manual#type", ["unpack"] = "mw:Extension:Scribunto/Lua reference manual#unpack", -- Lua debug library. ["debug"] = "mw:Extension:Scribunto/Lua reference manual#Debug library", ["debug.traceback"] = "mw:Extension:Scribunto/Lua reference manual#debug.traceback", -- Lua math library. ["math"] = "mw:Extension:Scribunto/Lua reference manual#Math library", ["math.abs"] = "mw:Extension:Scribunto/Lua reference manual#math.abs", ["math.acos"] = "mw:Extension:Scribunto/Lua reference manual#math.acos", ["math.asin"] = "mw:Extension:Scribunto/Lua reference manual#math.asin", ["math.atan"] = "mw:Extension:Scribunto/Lua reference manual#math.atan", ["math.atan2"] = "mw:Extension:Scribunto/Lua reference manual#math.atan2", ["math.ceil"] = "mw:Extension:Scribunto/Lua reference manual#math.ceil", ["math.cos"] = "mw:Extension:Scribunto/Lua reference manual#math.cos", ["math.cosh"] = "mw:Extension:Scribunto/Lua reference manual#math.cosh", ["math.deg"] = "mw:Extension:Scribunto/Lua reference manual#math.deg", ["math.exp"] = "mw:Extension:Scribunto/Lua reference manual#math.exp", ["math.floor"] = "mw:Extension:Scribunto/Lua reference manual#math.floor", ["math.fmod"] = "mw:Extension:Scribunto/Lua reference manual#math.fmod", ["math.frexp"] = "mw:Extension:Scribunto/Lua reference manual#math.frexp", ["math.huge"] = "mw:Extension:Scribunto/Lua reference manual#math.huge", ["math.ldexp"] = "mw:Extension:Scribunto/Lua reference manual#math.ldexp", ["math.log"] = "mw:Extension:Scribunto/Lua reference manual#math.log", ["math.log10"] = "mw:Extension:Scribunto/Lua reference manual#math.log10", ["math.max"] = "mw:Extension:Scribunto/Lua reference manual#math.max", ["math.min"] = "mw:Extension:Scribunto/Lua reference manual#math.min", ["math.modf"] = "mw:Extension:Scribunto/Lua reference manual#math.modf", ["math.pi"] = "mw:Extension:Scribunto/Lua reference manual#math.pi", ["math.pow"] = "mw:Extension:Scribunto/Lua reference manual#math.pow", ["math.rad"] = "mw:Extension:Scribunto/Lua reference manual#math.rad", ["math.random"] = "mw:Extension:Scribunto/Lua reference manual#math.random", ["math.randomseed"] = "mw:Extension:Scribunto/Lua reference manual#math.randomseed", ["math.sin"] = "mw:Extension:Scribunto/Lua reference manual#math.sin", ["math.sinh"] = "mw:Extension:Scribunto/Lua reference manual#math.sinh", ["math.sqrt"] = "mw:Extension:Scribunto/Lua reference manual#math.sqrt", ["math.tan"] = "mw:Extension:Scribunto/Lua reference manual#math.tan", ["math.tanh"] = "mw:Extension:Scribunto/Lua reference manual#math.tanh", -- Operating system library. ["os"] = "mw:Extension:Scribunto/Lua reference manual#Operating system library", ["os.clock"] = "mw:Extension:Scribunto/Lua reference manual#os.clock", ["os.date"] = "mw:Extension:Scribunto/Lua reference manual#os.date", ["os.difftime"] = "mw:Extension:Scribunto/Lua reference manual#os.difftime", ["os.time"] = "mw:Extension:Scribunto/Lua reference manual#os.time", -- Package library. ["package"] = "mw:Extension:Scribunto/Lua reference manual#Package library", ["package.loaded"] = "mw:Extension:Scribunto/Lua reference manual#package.loaded", ["package.loaders"] = "mw:Extension:Scribunto/Lua reference manual#package.loaders", ["package.preload"] = "mw:Extension:Scribunto/Lua reference manual#package.preload", ["package.seeall"] = "mw:Extension:Scribunto/Lua reference manual#package.seeall", -- Lua string library. ["string"] = "mw:Extension:Scribunto/Lua reference manual#String library", ["string.byte"] = "mw:Extension:Scribunto/Lua reference manual#string.byte", ["string.char"] = "mw:Extension:Scribunto/Lua reference manual#string.char", ["string.find"] = "mw:Extension:Scribunto/Lua reference manual#string.find", ["string.format"] = "mw:Extension:Scribunto/Lua reference manual#string.format", ["string.gmatch"] = "mw:Extension:Scribunto/Lua reference manual#string.gmatch", ["string.gsub"] = "mw:Extension:Scribunto/Lua reference manual#string.gsub", ["string.len"] = "mw:Extension:Scribunto/Lua reference manual#string.len", ["string.lower"] = "mw:Extension:Scribunto/Lua reference manual#string.lower", ["string.match"] = "mw:Extension:Scribunto/Lua reference manual#string.match", ["string.rep"] = "mw:Extension:Scribunto/Lua reference manual#string.rep", ["string.reverse"] = "mw:Extension:Scribunto/Lua reference manual#string.reverse", ["string.sub"] = "mw:Extension:Scribunto/Lua reference manual#string.sub", ["string.upper"] = "mw:Extension:Scribunto/Lua reference manual#string.upper", -- Lua table library. ["table"] = "mw:Extension:Scribunto/Lua reference manual#Table library", ["table.concat"] = "mw:Extension:Scribunto/Lua reference manual#table.concat", ["table.insert"] = "mw:Extension:Scribunto/Lua reference manual#table.insert", ["table.maxn"] = "mw:Extension:Scribunto/Lua reference manual#table.maxn", ["table.remove"] = "mw:Extension:Scribunto/Lua reference manual#table.remove", ["table.sort"] = "mw:Extension:Scribunto/Lua reference manual#table.sort", -- MediaWiki Scribunto library. ["mw"] = "mw:Extension:Scribunto/Lua reference manual", ["mw.allToString"] = "mw:Extension:Scribunto/Lua reference manual#mw.allToString", ["mw.clearLogBuffer"] = "mw:Extension:Scribunto/Lua reference manual#mw.clearLogBuffer", ["mw.clone"] = "mw:Extension:Scribunto/Lua reference manual#mw.clone", ["mw.executeFunction"] = "mw:Extension:Scribunto/Lua reference manual#mw.executeFunction", ["mw.executeModule"] = "mw:Extension:Scribunto/Lua reference manual#mw.executeModule", ["mw.getCurrentFrame"] = "mw:Extension:Scribunto/Lua reference manual#mw.getCurrentFrame", ["mw.getLogBuffer"] = "mw:Extension:Scribunto/Lua reference manual#mw.getLogBuffer", ["mw.incrementExpensiveFunctionCount"] = "mw:Extension:Scribunto/Lua reference manual#mw.incrementExpensiveFunctionCount", ["mw.loadData"] = "mw:Extension:Scribunto/Lua reference manual#mw.loadData", ["DataWrapper"] = "mw:Extension:Scribunto/Lua reference manual#mw.loadData", ["mw.log"] = "mw:Extension:Scribunto/Lua reference manual#mw.log", ["print"] = "mw:Extension:Scribunto/Lua reference manual#mw.log", -- Scribunto frame object. ["frame"] = "mw:Extension:Scribunto/Lua reference manual#Frame object", ["frame.args"] = "mw:Extension:Scribunto/Lua reference manual#frame.args", ["frame:getParent"] = "mw:Extension:Scribunto/Lua reference manual#frame:getParent", ["frame:expandTemplate"] = "mw:Extension:Scribunto/Lua reference manual#frame:expandTemplate", ["frame:preprocess"] = "mw:Extension:Scribunto/Lua reference manual#frame:preprocess", ["frame:getArgument"] = "mw:Extension:Scribunto/Lua reference manual#frame:getArgument", ["frame:newParserValue"] = "mw:Extension:Scribunto/Lua reference manual#frame:newParserValue", ["frame:newTemplateParserValue"] = "mw:Extension:Scribunto/Lua reference manual#frame:newTemplateParserValue", ["frame:argumentPairs"] = "mw:Extension:Scribunto/Lua reference manual#frame:argumentPairs", ["frame:getTitle"] = "mw:Extension:Scribunto/Lua reference manual#frame:getTitle", -- Scribunto frame object as `Frame` class. ["Frame"] = "mw:Extension:Scribunto/Lua reference manual#Frame object", ["Frame.args"] = "mw:Extension:Scribunto/Lua reference manual#frame.args", ["Frame:getParent"] = "mw:Extension:Scribunto/Lua reference manual#frame:getParent", ["Frame:expandTemplate"] = "mw:Extension:Scribunto/Lua reference manual#frame:expandTemplate", ["Frame:preprocess"] = "mw:Extension:Scribunto/Lua reference manual#frame:preprocess", ["Frame:getArgument"] = "mw:Extension:Scribunto/Lua reference manual#frame:getArgument", ["Frame:newParserValue"] = "mw:Extension:Scribunto/Lua reference manual#frame:newParserValue", ["Frame:newTemplateParserValue"] = "mw:Extension:Scribunto/Lua reference manual#frame:newTemplateParserValue", ["Frame:argumentPairs"] = "mw:Extension:Scribunto/Lua reference manual#frame:argumentPairs", ["Frame:getTitle"] = "mw:Extension:Scribunto/Lua reference manual#frame:getTitle", -- Scribunto language library. ["mw.language"] = "mw:Extension:Scribunto/Lua reference manual#Language library", ["mw.language.fetchLanguageName"] = "mw:Extension:Scribunto/Lua reference manual#mw.language.fetchLanguageName", ["mw.language.getContentLanguage"] = "mw:Extension:Scribunto/Lua reference manual#mw.language.getContentLanguage", ["mw.language.isValidBuiltInCode"] = "mw:Extension:Scribunto/Lua reference manual#mw.language.isValidBuiltInCode", ["mw.language.isValidCode"] = "mw:Extension:Scribunto/Lua reference manual#mw.language.isValidCode", ["mw.language.new"] = "mw:Extension:Scribunto/Lua reference manual#mw.language.new", -- Scribunto language object. ["lang"] = "mw:Extension:Scribunto/Lua reference manual#Language object", ["mw.language:getCode"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:getCode", ["mw.language:isRTL"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:isRTL", ["mw.language:lc"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:lc", ["mw.language:lcfirst"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:lcfirst", ["mw.language:uc"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:uc", ["mw.language:ucfirst"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:ucfirst", ["mw.language:caseFold"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:caseFold", ["mw.language:formatNum"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:formatNum", ["mw.language:formatDate"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:formatDate", ["mw.language:parseFormattedNumber"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:parseFormattedNumber", ["mw.language:convertPlural"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:convertPlural", ["mw.language:convertGrammar"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:convertGrammar", ["mw.language:gender"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:gender", -- Scribunto language object as `Lang` class. ["Lang"] = "mw:Extension:Scribunto/Lua reference manual#Language object", ["Lang:getCode"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:getCode", ["Lang:isRTL"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:isRTL", ["Lang:lc"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:lc", ["Lang:lcfirst"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:lcfirst", ["Lang:uc"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:uc", ["Lang:ucfirst"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:ucfirst", ["Lang:caseFold"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:caseFold", ["Lang:formatNum"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:formatNum", ["Lang:formatDate"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:formatDate", ["Lang:parseFormattedNumber"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:parseFormattedNumber", ["Lang:convertPlural"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:convertPlural", ["Lang:convertGrammar"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:convertGrammar", ["Lang:gender"] = "mw:Extension:Scribunto/Lua reference manual#mw.language:gender", -- Scribunto site library. ["mw.site"] = "mw:Extension:Scribunto/Lua reference manual#Site library", ["mw.site.currentVersion"] = "mw:Extension:Scribunto/Lua reference manual#mw.site.currentVersion", ["mw.site.scriptPath"] = "mw:Extension:Scribunto/Lua reference manual#mw.site.scriptPath", ["mw.site.server"] = "mw:Extension:Scribunto/Lua reference manual#mw.site.server", ["mw.site.siteName"] = "mw:Extension:Scribunto/Lua reference manual#mw.site.siteName", ["mw.site.stylePath"] = "mw:Extension:Scribunto/Lua reference manual#mw.site.stylePath", ["mw.site.namespaces"] = "mw:Extension:Scribunto/Lua reference manual#mw.site.namespaces", ["mw.site.contentNamespaces"] = "mw:Extension:Scribunto/Lua reference manual#mw.site.contentNamespaces", ["mw.site.subjectNamespaces"] = "mw:Extension:Scribunto/Lua reference manual#mw.site.subjectNamespaces", ["mw.site.talkNamespaces"] = "mw:Extension:Scribunto/Lua reference manual#mw.site.talkNamespaces", ["mw.site.sassParams"] = "mw:Extension:Scribunto/Lua reference manual#mw.site.sassParams", ["mw.site.stats"] = "mw:Extension:Scribunto/Lua reference manual#mw.site.stats", ["mw.site.stats.pagesInCategory"] = "mw:Extension:Scribunto/Lua reference manual#mw.site.stats.pagesInCategory", ["mw.site.stats.pagesInNamespace"] = "mw:Extension:Scribunto/Lua reference manual#mw.site.stats.pagesInNamespace", ["mw.site.stats.usersInGroup"] = "mw:Extension:Scribunto/Lua reference manual#mw.site.stats.usersInGroup", -- Scribunto URI library. ["mw.uri"] = "mw:Extension:Scribunto/Lua reference manual#Uri library", ["mw.uri.encode"] = "mw:Extension:Scribunto/Lua reference manual#mw.uri.encode", ["mw.uri.decode"] = "mw:Extension:Scribunto/Lua reference manual#mw.uri.decode", ["mw.uri.anchorEncode"] = "mw:Extension:Scribunto/Lua reference manual#mw.uri.anchorEncode", ["mw.uri.buildQueryString"] = "mw:Extension:Scribunto/Lua reference manual#mw.uri.buildQueryString", ["mw.uri.parseQueryString"] = "mw:Extension:Scribunto/Lua reference manual#mw.uri.parseQueryString", ["mw.uri.canonicalUrl"] = "mw:Extension:Scribunto/Lua reference manual#mw.uri.canonicalUrl", ["mw.uri.fullUrl"] = "mw:Extension:Scribunto/Lua reference manual#mw.uri.fullUrl", ["mw.uri.localUrl"] = "mw:Extension:Scribunto/Lua reference manual#mw.uri.localUrl", ["mw.uri.new"] = "mw:Extension:Scribunto/Lua reference manual#mw.uri.new", ["mw.uri.validate"] = "mw:Extension:Scribunto/Lua reference manual#mw.uri.validate", -- Scribunto URI object. ["uri"] = "mw:Extension:Scribunto/Lua reference manual#URI object", ["mw.uri:parse"] = "mw:Extension:Scribunto/Lua reference manual#mw.uri:parse", ["mw.uri:clone"] = "mw:Extension:Scribunto/Lua reference manual#mw.uri:clone", ["mw.uri:extend"] = "mw:Extension:Scribunto/Lua reference manual#mw.uri:extend", -- Scribunto URI object as `Uri` class. ["Uri"] = "mw:Extension:Scribunto/Lua reference manual#URI object", ["Uri:parse"] = "mw:Extension:Scribunto/Lua reference manual#mw.uri:parse", ["Uri:parse"] = "mw:Extension:Scribunto/Lua reference manual#mw.uri:parse", ["Uri:clone"] = "mw:Extension:Scribunto/Lua reference manual#mw.uri:clone", ["Uri:extend"] = "mw:Extension:Scribunto/Lua reference manual#mw.uri:extend", -- Scribunto UTF8-safe string library. ["mw.ustring"] = "mw:Extension:Scribunto/Lua reference manual#Ustring library", ["mw.ustring.maxPatternLength"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.maxPatternLength", ["mw.ustring.maxStringLength"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.maxStringLength", ["mw.ustring.byte"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.byte", ["mw.ustring.byteoffset"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.byteoffset", ["mw.ustring.char"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.char", ["mw.ustring.codepoint"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.codepoint", ["mw.ustring.find"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.find", ["mw.ustring.format"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.format", ["mw.ustring.gcodepoint"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.gcodepoint", ["mw.ustring.gmatch"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.gmatch", ["mw.ustring.gsub"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.gsub", ["mw.ustring.isutf8"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.isutf8", ["mw.ustring.len"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.len", ["mw.ustring.lower"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.lower", ["mw.ustring.match"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.match", ["mw.ustring.rep"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.rep", ["mw.ustring.sub"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.sub", ["mw.ustring.toNFC"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.toNFC", ["mw.ustring.toNFD"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.toNFD", ["mw.ustring.upper"] = "mw:Extension:Scribunto/Lua reference manual#mw.ustring.upper", -- Scribunto HTML library. ["mw.html"] = "mw:Extension:Scribunto/Lua reference manual#HTML library", ["mw.html.create"] = "mw:Extension:Scribunto/Lua reference manual#mw.html.create", -- Scribunto HTML object. ["html"] = "mw:Extension:Scribunto/Lua reference manual#HTML object", ["mw.html:node"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:node", ["mw.html:wikitext"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:wikitext", ["mw.html:newline"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:newline", ["mw.html:tag"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:tag", ["mw.html:attr"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:attr", ["mw.html:getAttr"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:getAttr", ["mw.html:addClass"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:addClass", ["mw.html:css"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:css", ["mw.html:cssText"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:cssText", ["mw.html:done"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:done", ["mw.html:allDone"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:allDone", -- Scribunto HTML object as `HTMLBuilder` class. ["HTMLBuilder"] = "mw:Extension:Scribunto/Lua reference manual#HTML object", ["HTMLBuilder:node"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:node", ["HTMLBuilder:wikitext"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:wikitext", ["HTMLBuilder:newline"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:newline", ["HTMLBuilder:tag"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:tag", ["HTMLBuilder:attr"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:attr", ["HTMLBuilder:getAttr"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:getAttr", ["HTMLBuilder:addClass"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:addClass", ["HTMLBuilder:css"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:css", ["HTMLBuilder:cssText"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:cssText", ["HTMLBuilder:done"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:done", ["HTMLBuilder:allDone"] = "mw:Extension:Scribunto/Lua reference manual#mw.html:allDone", -- Scribunto text library. ["mw.text"] = "mw:Extension:Scribunto/Lua reference manual#Text library", ["mw.text.decode"] = "mw:Extension:Scribunto/Lua reference manual#mw.text.decode", ["mw.text.encode"] = "mw:Extension:Scribunto/Lua reference manual#mw.text.encode", ["mw.text.listToText"] = "mw:Extension:Scribunto/Lua reference manual#mw.text.listToText", ["mw.text.nowiki"] = "mw:Extension:Scribunto/Lua reference manual#mw.text.nowiki", ["mw.text.split"] = "mw:Extension:Scribunto/Lua reference manual#mw.text.split", ["mw.text.gsplit"] = "mw:Extension:Scribunto/Lua reference manual#mw.text.gsplit", ["mw.text.tag"] = "mw:Extension:Scribunto/Lua reference manual#mw.text.tag", ["mw.text.trim"] = "mw:Extension:Scribunto/Lua reference manual#mw.text.trim", ["mw.text.truncate"] = "mw:Extension:Scribunto/Lua reference manual#mw.text.truncate", ["mw.text.unstrip"] = "mw:Extension:Scribunto/Lua reference manual#mw.text.unstrip", -- Scribunto title library. ["mw.title"] = "mw:Extension:Scribunto/Lua reference manual#Title library", ["mw.title.equals"] = "mw:Extension:Scribunto/Lua reference manual#mw.title.equals", ["mw.title.compare"] = "mw:Extension:Scribunto/Lua reference manual#mw.title.compare", ["mw.title.getCurrentTitle"] = "mw:Extension:Scribunto/Lua reference manual#mw.title.getCurrentTile", ["mw.title.new"] = "mw:Extension:Scribunto/Lua reference manual#mw.title.new", ["mw.title.makeTitle"] = "mw:Extension:Scribunto/Lua reference manual#mw.title.makeTitle", -- Scribunto title object. ["mw.title.id"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.interwiki"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.namespace"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.fragment"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.nsText"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.subjectNsText"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.text"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.prefixedText"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.fullText"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.rootText"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.baseText"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.subpageText"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.canTalk"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.exists"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.fileExists"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.isContentPage"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.isExternal"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.isLocal"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.isRedirect"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.isSpecialPage"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.isSubpage"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.isTalkPage"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title:isSubpageOf"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title:inNamespace"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title:inNamespaces"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title:hasSubjectNamespace"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.basePageTitle"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.rootPageTitle"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.talkPageTitle"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.subjectPageTitle"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title.protectionLevels"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title:subPageTitle"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title:partialUrl"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title:fullUrl"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title:localUrl"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title:canonicalUrl"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["mw.title:getContent"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", -- Scribunto title object as `Title` class. ["Title"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.id"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.interwiki"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.namespace"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.fragment"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.nsText"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.subjectNsText"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.text"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.prefixedText"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.fullText"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.rootText"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.baseText"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.subpageText"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.canTalk"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.exists"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.fileExists"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.isContentPage"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.isExternal"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.isLocal"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.isRedirect"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.isSpecialPage"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.isSubpage"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.isTalkPage"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title:isSubpageOf"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title:inNamespace"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title:inNamespaces"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title:hasSubjectNamespace"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.basePageTitle"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.rootPageTitle"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.talkPageTitle"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.subjectPageTitle"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title.protectionLevels"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title:subPageTitle"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title:partialUrl"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title:fullUrl"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title:localUrl"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title:canonicalUrl"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", ["Title:getContent"] = "mw:Extension:Scribunto/Lua reference manual#Title objects", -- Scribunto message library. ["mw.message"] = "mw:Extension:Scribunto/Lua reference manual#Message library", ["mw.message.new"] = "mw:Extension:Scribunto/Lua reference manual#mw.message.new", ["mw.message.newFallbackSequence"] = "mw:Extension:Scribunto/Lua reference manual#mw.message.newFallbackSequence", ["mw.message.rawParam"] = "mw:Extension:Scribunto/Lua reference manual#mw.message.rawParam", ["mw.message.numParam"] = "mw:Extension:Scribunto/Lua reference manual#mw.message.numParam", ["mw.message.getDefaultLanguage"] = "mw:Extension:Scribunto/Lua reference manual#mw.message.getDefaultLanguage", -- Scribunto message object. ["msg"] = "mw:Extension:Scribunto/Lua reference manual#Message object", ["mw.message:params"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:params", ["mw.message:rawParams"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:rawParams", ["mw.message:numParams"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:numParams", ["mw.message:inLanguage"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:inLanguage", ["mw.message:useDatabase"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:useDatabase", ["mw.message:plain"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:plain", ["mw.message:exists"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:exists", ["mw.message:isBlank"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:isBlank", ["mw.message:isDisabled"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:isDisabled", -- Scribunto message object as `Msg` class. ["Msg"] = "mw:Extension:Scribunto/Lua reference manual#Message object", ["Msg:params"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:params", ["Msg:rawParams"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:rawParams", ["Msg:numParams"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:numParams", ["Msg:inLanguage"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:inLanguage", ["Msg:useDatabase"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:useDatabase", ["Msg:plain"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:plain", ["Msg:exists"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:exists", ["Msg:isBlank"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:isBlank", ["Msg:isDisabled"] = "mw:Extension:Scribunto/Lua reference manual#mw.message:isDisabled", } } mjxb8skj02djmabwzn0kbg2eptjb0r6 Module:Lua lexer 828 101353 803406 2026-06-08T03:21:07Z en>Awesome Aasim 0 remove header but link to MIT license 803406 Scribunto text/plain --- Lexer for Lua source code written in pure Lua. -- @script lexer -- @license [https://github.com/LoganDark/lua-lexer/blob/master/LICENSE MIT] -- @author https://github.com/LoganDark -- @param {string} text Lua source code to lex. -- @return {string} Table of line arrays containing lexemes. --- Mapper for individual token list string. -- @param {string} src List of characters or keywords to map. -- @param[opt] {table} list Table to extend by reference. -- @return {{char=true,...}}, map -- @local local function lookupify(src, list) list = list or {} if type(src) == 'string' then for i = 1, src:len() do list[src:sub(i, i)] = true end elseif type(src) == 'table' then for i = 1, #src do list[src[i]] = true end end return list end --- Base identifier character set. -- @variable {string} base_ident local base_ident = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_' --- Base identifier character set. -- @variable {string} base_digits local base_digits = '0123456789' --- Base identifier character set. -- @variable {string} base_operators local base_operators = '+-*/^%#' --- Map of Lua character patterns. -- @table chars -- @field {table} whitespace Boolean map of whitespace -- tokens. -- @field {table} validEscapes Boolean map of valid escape -- characters. -- @field {table} ident Boolean map of valid identifier -- characters. -- @field {table} symbols Boolean map of valid symbol and -- operator characters. -- @local local chars = { whitespace = lookupify(' \n\t\r'), validEscapes = lookupify('abfnrtv"\'\\'), ident = lookupify( base_ident .. base_digits, { start = lookupify(base_ident), } ), digits = lookupify( base_digits, { hex = lookupify(base_digits .. 'abcdefABCDEF') } ), symbols = lookupify( base_operators .. ',{}[]();.:', { equality = lookupify('~=><'), operators = lookupify(base_operators) } ) } --- List of Lua keywords. -- @table keywords -- @field structure Boolean map of structure keywords. -- @field values Boolean map of primitive keywords. local keywords = { structure = lookupify({ 'and', 'break', 'do', 'else', 'elseif', 'end', 'for', 'function', 'goto', 'if', 'in', 'local', 'not', 'or', 'repeat', 'return', 'then', 'until', 'while' }), values = lookupify({ 'true', 'false', 'nil' }) } -- Lexer function export. return function(text) local pos = 1 local start = 1 local buffer = {} local lines = {} local function look(delta) delta = pos + (delta or 0) return text:sub(delta, delta) end local function get() pos = pos + 1 return look(-1) end local function getDataLevel() local num = 0 while look(num) == '=' do num = num + 1 end if look(num) == '[' then pos = pos + num + 1 return num end end local function getCurrentTokenText() return text:sub(start, pos - 1) end local currentLineLength = 0 local lineoffset = 0 local function pushToken(type, text) text = text or getCurrentTokenText() local tk = buffer[#buffer] if not tk or tk.type ~= type then tk = { type = type, data = text, posFirst = start - lineoffset, posLast = pos - 1 - lineoffset } if tk.data ~= '' then buffer[#buffer + 1] = tk end else tk.data = tk.data .. text tk.posLast = tk.posLast + text:len() end currentLineLength = currentLineLength + text:len() start = pos return tk end local function newline() lines[#lines + 1] = buffer buffer = {} get() pushToken('newline') buffer[1] = nil lineoffset = lineoffset + currentLineLength currentLineLength = 0 end local function getData(level, type) while true do local char = get() if char == '' then return elseif char == '\n' then pos = pos - 1 pushToken(type) newline() elseif char == ']' then local valid = true for i = 1, level do if look() == '=' then pos = pos + 1 else valid = false break end end if valid and look() == ']' then pos = pos - level - 1 return end end end end local function chompWhitespace() while true do local char = look() if char == '\n' then pushToken('whitespace') newline() elseif chars.whitespace[char] then pos = pos + 1 else break end end pushToken('whitespace') end while true do chompWhitespace() local char = get() if char == '' then break elseif char == '-' and look() == '-' then pos = pos + 1 if look() == '[' then pos = pos + 1 local level = getDataLevel() if level then getData(level, 'comment') pos = pos + level + 2 pushToken('comment') else while true do local char2 = get() if char2 == '' or char2 == '\n' then pos = pos - 1 pushToken('comment') if char2 == '\n' then newline() end break end end end else while true do local char2 = get() if char2 == '' or char2 == '\n' then pos = pos - 1 pushToken('comment') if char2 == '\n' then newline() end break end end end pushToken('comment') elseif char == '\'' or char == '"' then pushToken('string_start') while true do local char2 = get() if char2 == '\\' then pos = pos - 1 pushToken('string') get() local char3 = get() if chars.digits[char3] then for i = 1, 2 do if chars.digits[look()] then pos = pos + 1 end end elseif char3 == 'x' then if chars.digits.hex[look()] and chars.digits.hex[look(1)] then pos = pos + 2 else pushToken('unidentified') end elseif char3 == '\n' then pos = pos - 1 pushToken('escape') newline() elseif not chars.validEscapes[char3] then pushToken('unidentified') end pushToken('escape') elseif char2 == '\n' then pos = pos - 1 pushToken('string') newline() break elseif char2 == char or char2 == '' then pos = pos - 1 pushToken('string') get() break end end pushToken('string_end') elseif chars.ident.start[char] then while chars.ident[look()] do pos = pos + 1 end local word = getCurrentTokenText() if keywords.structure[word] then pushToken('keyword') elseif keywords.values[word] then pushToken('value') else pushToken('ident') end elseif chars.digits[char] or (char == '.' and chars.digits[look()]) then if char == '0' and look() == 'x' then pos = pos + 1 while chars.digits.hex[look()] do pos = pos + 1 end else while chars.digits[look()] do pos = pos + 1 end if look() == '.' then pos = pos + 1 while chars.digits[look()] do pos = pos + 1 end end if look():lower() == 'e' then pos = pos + 1 if look() == '-' then pos = pos + 1 end while chars.digits[look()] do pos = pos + 1 end end end pushToken('number') elseif char == '[' then local level = getDataLevel() if level then pushToken('string_start') getData(level, 'string') pushToken('string') pos = pos + level + 2 pushToken('string_end') else pushToken('symbol') end elseif char == '.' then if look() == '.' then pos = pos + 1 if look() == '.' then pos = pos + 1 end end if getCurrentTokenText():len() == 3 then pushToken('vararg') else pushToken('symbol') end elseif char == ':' and look() == ':' then get() pushToken('label_start') chompWhitespace() if chars.ident.start[look()] then get() while chars.ident[look()] do get() end pushToken('label') chompWhitespace() if look() == ':' and look(1) == ':' then get() get() pushToken('label_end') end end elseif chars.symbols.equality[char] then if look() == '=' then pos = pos + 1 end pushToken('operator') elseif chars.symbols[char] then if chars.symbols.operators[char] then pushToken('operator') else pushToken('symbol') end else pushToken('unidentified') end end lines[#lines + 1] = buffer return lines end kmv09oaxzsoiuyngaiw84n7j6vfbjmq 803407 803406 2026-07-29T23:41:47Z SM7 3953 1 revision imported from [[:en:Module:Lua_lexer]] 803406 Scribunto text/plain --- Lexer for Lua source code written in pure Lua. -- @script lexer -- @license [https://github.com/LoganDark/lua-lexer/blob/master/LICENSE MIT] -- @author https://github.com/LoganDark -- @param {string} text Lua source code to lex. -- @return {string} Table of line arrays containing lexemes. --- Mapper for individual token list string. -- @param {string} src List of characters or keywords to map. -- @param[opt] {table} list Table to extend by reference. -- @return {{char=true,...}}, map -- @local local function lookupify(src, list) list = list or {} if type(src) == 'string' then for i = 1, src:len() do list[src:sub(i, i)] = true end elseif type(src) == 'table' then for i = 1, #src do list[src[i]] = true end end return list end --- Base identifier character set. -- @variable {string} base_ident local base_ident = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_' --- Base identifier character set. -- @variable {string} base_digits local base_digits = '0123456789' --- Base identifier character set. -- @variable {string} base_operators local base_operators = '+-*/^%#' --- Map of Lua character patterns. -- @table chars -- @field {table} whitespace Boolean map of whitespace -- tokens. -- @field {table} validEscapes Boolean map of valid escape -- characters. -- @field {table} ident Boolean map of valid identifier -- characters. -- @field {table} symbols Boolean map of valid symbol and -- operator characters. -- @local local chars = { whitespace = lookupify(' \n\t\r'), validEscapes = lookupify('abfnrtv"\'\\'), ident = lookupify( base_ident .. base_digits, { start = lookupify(base_ident), } ), digits = lookupify( base_digits, { hex = lookupify(base_digits .. 'abcdefABCDEF') } ), symbols = lookupify( base_operators .. ',{}[]();.:', { equality = lookupify('~=><'), operators = lookupify(base_operators) } ) } --- List of Lua keywords. -- @table keywords -- @field structure Boolean map of structure keywords. -- @field values Boolean map of primitive keywords. local keywords = { structure = lookupify({ 'and', 'break', 'do', 'else', 'elseif', 'end', 'for', 'function', 'goto', 'if', 'in', 'local', 'not', 'or', 'repeat', 'return', 'then', 'until', 'while' }), values = lookupify({ 'true', 'false', 'nil' }) } -- Lexer function export. return function(text) local pos = 1 local start = 1 local buffer = {} local lines = {} local function look(delta) delta = pos + (delta or 0) return text:sub(delta, delta) end local function get() pos = pos + 1 return look(-1) end local function getDataLevel() local num = 0 while look(num) == '=' do num = num + 1 end if look(num) == '[' then pos = pos + num + 1 return num end end local function getCurrentTokenText() return text:sub(start, pos - 1) end local currentLineLength = 0 local lineoffset = 0 local function pushToken(type, text) text = text or getCurrentTokenText() local tk = buffer[#buffer] if not tk or tk.type ~= type then tk = { type = type, data = text, posFirst = start - lineoffset, posLast = pos - 1 - lineoffset } if tk.data ~= '' then buffer[#buffer + 1] = tk end else tk.data = tk.data .. text tk.posLast = tk.posLast + text:len() end currentLineLength = currentLineLength + text:len() start = pos return tk end local function newline() lines[#lines + 1] = buffer buffer = {} get() pushToken('newline') buffer[1] = nil lineoffset = lineoffset + currentLineLength currentLineLength = 0 end local function getData(level, type) while true do local char = get() if char == '' then return elseif char == '\n' then pos = pos - 1 pushToken(type) newline() elseif char == ']' then local valid = true for i = 1, level do if look() == '=' then pos = pos + 1 else valid = false break end end if valid and look() == ']' then pos = pos - level - 1 return end end end end local function chompWhitespace() while true do local char = look() if char == '\n' then pushToken('whitespace') newline() elseif chars.whitespace[char] then pos = pos + 1 else break end end pushToken('whitespace') end while true do chompWhitespace() local char = get() if char == '' then break elseif char == '-' and look() == '-' then pos = pos + 1 if look() == '[' then pos = pos + 1 local level = getDataLevel() if level then getData(level, 'comment') pos = pos + level + 2 pushToken('comment') else while true do local char2 = get() if char2 == '' or char2 == '\n' then pos = pos - 1 pushToken('comment') if char2 == '\n' then newline() end break end end end else while true do local char2 = get() if char2 == '' or char2 == '\n' then pos = pos - 1 pushToken('comment') if char2 == '\n' then newline() end break end end end pushToken('comment') elseif char == '\'' or char == '"' then pushToken('string_start') while true do local char2 = get() if char2 == '\\' then pos = pos - 1 pushToken('string') get() local char3 = get() if chars.digits[char3] then for i = 1, 2 do if chars.digits[look()] then pos = pos + 1 end end elseif char3 == 'x' then if chars.digits.hex[look()] and chars.digits.hex[look(1)] then pos = pos + 2 else pushToken('unidentified') end elseif char3 == '\n' then pos = pos - 1 pushToken('escape') newline() elseif not chars.validEscapes[char3] then pushToken('unidentified') end pushToken('escape') elseif char2 == '\n' then pos = pos - 1 pushToken('string') newline() break elseif char2 == char or char2 == '' then pos = pos - 1 pushToken('string') get() break end end pushToken('string_end') elseif chars.ident.start[char] then while chars.ident[look()] do pos = pos + 1 end local word = getCurrentTokenText() if keywords.structure[word] then pushToken('keyword') elseif keywords.values[word] then pushToken('value') else pushToken('ident') end elseif chars.digits[char] or (char == '.' and chars.digits[look()]) then if char == '0' and look() == 'x' then pos = pos + 1 while chars.digits.hex[look()] do pos = pos + 1 end else while chars.digits[look()] do pos = pos + 1 end if look() == '.' then pos = pos + 1 while chars.digits[look()] do pos = pos + 1 end end if look():lower() == 'e' then pos = pos + 1 if look() == '-' then pos = pos + 1 end while chars.digits[look()] do pos = pos + 1 end end end pushToken('number') elseif char == '[' then local level = getDataLevel() if level then pushToken('string_start') getData(level, 'string') pushToken('string') pos = pos + level + 2 pushToken('string_end') else pushToken('symbol') end elseif char == '.' then if look() == '.' then pos = pos + 1 if look() == '.' then pos = pos + 1 end end if getCurrentTokenText():len() == 3 then pushToken('vararg') else pushToken('symbol') end elseif char == ':' and look() == ':' then get() pushToken('label_start') chompWhitespace() if chars.ident.start[look()] then get() while chars.ident[look()] do get() end pushToken('label') chompWhitespace() if look() == ':' and look(1) == ':' then get() get() pushToken('label_end') end end elseif chars.symbols.equality[char] then if look() == '=' then pos = pos + 1 end pushToken('operator') elseif chars.symbols[char] then if chars.symbols.operators[char] then pushToken('operator') else pushToken('symbol') end else pushToken('unidentified') end end lines[#lines + 1] = buffer return lines end kmv09oaxzsoiuyngaiw84n7j6vfbjmq Module:Unindent 828 101354 803408 2026-01-30T15:31:11Z en>Awesome Aasim 0 803408 Scribunto text/plain --[[ Copyright (c) 2013 Enrique García Cota Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --]] -- <nowiki> --- Unindent resets the indentation level of mulitline strings. -- It is useful for multiline strings inside functions and large tables. -- This module serves as a utility function for string parsing. -- -- Lua supports multiline strings in the format `[[\n...\n]]`. In general, -- Lua does not outdent indented multiline strings out of the box. Though -- Lua supports variable indentation in multiline strings, custom logic is -- necessary to reset the string's indentation. This module adopts a -- flexible approach based on string scanning. -- -- Unlike Penlight's `pl.text.dedent` behaviour where every line has the -- indentation of the first line removed, the line prefixed with the least -- non-tab whitespace is reset to zero indentation. Thus, the opening line -- of the string may retain some indentation *if* there are lines of less -- indentation terminating the string. -- -- @script unindent -- @license MIT -- @release stable -- @author [[wikia:dev:User:8nml|8nml]] -- @attribution [https://github.com/kikito @kikito] ([https://github.com/kikito/inspect.lua/blob/master/spec/unindent.lua GitHub]) -- @param {string} str Multiline string indented consistently. -- @returns {string} Unindented string. return function(str) str = str:gsub(' +$', ''):gsub('^ +', '') -- remove spaces at start and end local level = math.huge local minPrefix = '' local len for prefix in str:gmatch('\n( +)') do len = #prefix if len < level then level = len minPrefix = prefix end end return (str:gsub('\n' .. minPrefix, '\n'):gsub('\n$', '')) end njtcm3z8uvh69ku6zyaxoc681v6dcko 803409 803408 2026-07-29T23:42:02Z SM7 3953 1 revision imported from [[:en:Module:Unindent]] 803408 Scribunto text/plain --[[ Copyright (c) 2013 Enrique García Cota Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --]] -- <nowiki> --- Unindent resets the indentation level of mulitline strings. -- It is useful for multiline strings inside functions and large tables. -- This module serves as a utility function for string parsing. -- -- Lua supports multiline strings in the format `[[\n...\n]]`. In general, -- Lua does not outdent indented multiline strings out of the box. Though -- Lua supports variable indentation in multiline strings, custom logic is -- necessary to reset the string's indentation. This module adopts a -- flexible approach based on string scanning. -- -- Unlike Penlight's `pl.text.dedent` behaviour where every line has the -- indentation of the first line removed, the line prefixed with the least -- non-tab whitespace is reset to zero indentation. Thus, the opening line -- of the string may retain some indentation *if* there are lines of less -- indentation terminating the string. -- -- @script unindent -- @license MIT -- @release stable -- @author [[wikia:dev:User:8nml|8nml]] -- @attribution [https://github.com/kikito @kikito] ([https://github.com/kikito/inspect.lua/blob/master/spec/unindent.lua GitHub]) -- @param {string} str Multiline string indented consistently. -- @returns {string} Unindented string. return function(str) str = str:gsub(' +$', ''):gsub('^ +', '') -- remove spaces at start and end local level = math.huge local minPrefix = '' local len for prefix in str:gmatch('\n( +)') do len = #prefix if len < level then level = len minPrefix = prefix end end return (str:gsub('\n' .. minPrefix, '\n'):gsub('\n$', '')) end njtcm3z8uvh69ku6zyaxoc681v6dcko Module:WikidataIB/nolinks 828 101355 803478 2019-01-01T18:19:33Z en>RexxS 0 create module to define some common terms which should not be linked. 803478 Scribunto text/plain local p ={} --[[ The values here are the English sitelinks for items that should not be linked. These 36 are not defininitive and may be altered to suit. --]] p.items = { "Australia", "Austria", "Belgium", "Canada", "China", "Denmark", "England", "France", "Germany", "Greece", "Hungary", "Iceland", "India", "Republic of Ireland", "Israel", "Italy", "Jamaica", "Japan", "Luxembourg", "Mexico", "Netherlands", "New Zealand", "Northern Ireland", "Norway", "Poland", "Portugal", "Russia", "Scotland", "South Africa", "Spain", "Sweden", "Switzerland", "Turkey", "United Kingdom", "United States", "Wales", } --[[ This provides a convenient way to create a test whether an item is on the list. --]] p.itemsindex = {} for i, v in ipairs(items) do itemsindex[v] = true end return p avfgdf3p4rlq80n3vttiwse2rc8o5jl 803479 803478 2019-01-01T18:49:12Z en>RexxS 0 p.itemsindex and p.items 803479 Scribunto text/plain local p ={} --[[ The values here are the English sitelinks for items that should not be linked. These 36 are not defininitive and may be altered to suit. --]] p.items = { "Australia", "Austria", "Belgium", "Canada", "China", "Denmark", "England", "France", "Germany", "Greece", "Hungary", "Iceland", "India", "Republic of Ireland", "Israel", "Italy", "Jamaica", "Japan", "Luxembourg", "Mexico", "Netherlands", "New Zealand", "Northern Ireland", "Norway", "Poland", "Portugal", "Russia", "Scotland", "South Africa", "Spain", "Sweden", "Switzerland", "Turkey", "United Kingdom", "United States", "Wales", } --[[ This provides a convenient way to create a test whether an item is on the list. --]] p.itemsindex = {} for i, v in ipairs(p.items) do p.itemsindex[v] = true end return p k76cymw995xwvtr574mvyq05caggbdc 803480 803479 2019-01-07T01:22:30Z 71.163.7.214 803480 Scribunto text/plain local p ={} --[[ The values here are the English sitelinks for items that should not be linked. These 36 are not defininitive and may be altered to suit. --]] p.items = { "Austral", "Austria", "Belgium", "Canada", "China", "Denmark", "England", "France", "Germany", "Greece", "Hungary", "Iceland", "India", "Republic of Ireland", "Israel", "Italy", "Jamaica", "Japan", "Luxembourg", "Mexico", "Netherlands", "New Zealand", "Northern Ireland", "Norway", "Poland", "Portugal", "Russia", "Scotland", "South Africa", "Spain", "Sweden", "Switzerland", "Turkey", "United Kingdom", "United States", "Wales", } --[[ This provides a convenient way to create a test whether an item is on the list. --]] p.itemsindex = {} for i, v in ipairs(p.items) do p.itemsindex[v] = true end return p c060vtph4yywz2noowizhotdxl1oqma 803481 803480 2019-01-07T02:44:02Z en>RexxS 0 Reverted edits by [[Special:Contribs/71.163.7.214|71.163.7.214]] ([[User talk:71.163.7.214|talk]]) to last version by RexxS 803481 Scribunto text/plain local p ={} --[[ The values here are the English sitelinks for items that should not be linked. These 36 are not defininitive and may be altered to suit. --]] p.items = { "Australia", "Austria", "Belgium", "Canada", "China", "Denmark", "England", "France", "Germany", "Greece", "Hungary", "Iceland", "India", "Republic of Ireland", "Israel", "Italy", "Jamaica", "Japan", "Luxembourg", "Mexico", "Netherlands", "New Zealand", "Northern Ireland", "Norway", "Poland", "Portugal", "Russia", "Scotland", "South Africa", "Spain", "Sweden", "Switzerland", "Turkey", "United Kingdom", "United States", "Wales", } --[[ This provides a convenient way to create a test whether an item is on the list. --]] p.itemsindex = {} for i, v in ipairs(p.items) do p.itemsindex[v] = true end return p k76cymw995xwvtr574mvyq05caggbdc 803482 803481 2019-01-07T02:45:15Z en>RexxS 0 sp 803482 Scribunto text/plain local p ={} --[[ The values here are the English sitelinks for items that should not be linked. These 36 are not definitive and may be altered to suit. --]] p.items = { "Australia", "Austria", "Belgium", "Canada", "China", "Denmark", "England", "France", "Germany", "Greece", "Hungary", "Iceland", "India", "Republic of Ireland", "Israel", "Italy", "Jamaica", "Japan", "Luxembourg", "Mexico", "Netherlands", "New Zealand", "Northern Ireland", "Norway", "Poland", "Portugal", "Russia", "Scotland", "South Africa", "Spain", "Sweden", "Switzerland", "Turkey", "United Kingdom", "United States", "Wales", } --[[ This provides a convenient way to create a test whether an item is on the list. --]] p.itemsindex = {} for i, v in ipairs(p.items) do p.itemsindex[v] = true end return p i1pgsfuagq0i2dmjnfb0y02n1hiitd0 803483 803482 2019-01-07T03:17:02Z en>Ad Orientem 0 Protected "[[Module:WikidataIB/nolinks]]": High-risk Lua module: conforming protection to [[Module:WikidataIB]] and allow extended confirmed editors to edit. Per request at RfPP ([Edit=Require extended confirmed access] (indefinite) [Move=Require extended confirmed access] (indefinite)) 803482 Scribunto text/plain local p ={} --[[ The values here are the English sitelinks for items that should not be linked. These 36 are not definitive and may be altered to suit. --]] p.items = { "Australia", "Austria", "Belgium", "Canada", "China", "Denmark", "England", "France", "Germany", "Greece", "Hungary", "Iceland", "India", "Republic of Ireland", "Israel", "Italy", "Jamaica", "Japan", "Luxembourg", "Mexico", "Netherlands", "New Zealand", "Northern Ireland", "Norway", "Poland", "Portugal", "Russia", "Scotland", "South Africa", "Spain", "Sweden", "Switzerland", "Turkey", "United Kingdom", "United States", "Wales", } --[[ This provides a convenient way to create a test whether an item is on the list. --]] p.itemsindex = {} for i, v in ipairs(p.items) do p.itemsindex[v] = true end return p i1pgsfuagq0i2dmjnfb0y02n1hiitd0 803484 803483 2019-01-24T14:07:29Z en>Galobtter 0 Changed protection level for "[[Module:WikidataIB/nolinks]]": [[WP:High-risk templates|High-risk Lua module]]: 600000+ transclusions ([Edit=Require template editor access] (indefinite) [Move=Require template editor access] (indefinite)) 803482 Scribunto text/plain local p ={} --[[ The values here are the English sitelinks for items that should not be linked. These 36 are not definitive and may be altered to suit. --]] p.items = { "Australia", "Austria", "Belgium", "Canada", "China", "Denmark", "England", "France", "Germany", "Greece", "Hungary", "Iceland", "India", "Republic of Ireland", "Israel", "Italy", "Jamaica", "Japan", "Luxembourg", "Mexico", "Netherlands", "New Zealand", "Northern Ireland", "Norway", "Poland", "Portugal", "Russia", "Scotland", "South Africa", "Spain", "Sweden", "Switzerland", "Turkey", "United Kingdom", "United States", "Wales", } --[[ This provides a convenient way to create a test whether an item is on the list. --]] p.itemsindex = {} for i, v in ipairs(p.items) do p.itemsindex[v] = true end return p i1pgsfuagq0i2dmjnfb0y02n1hiitd0 803485 803484 2019-01-31T16:27:33Z en>RexxS 0 add abbreviations UK and USA 803485 Scribunto text/plain local p ={} --[[ The values here are the English sitelinks for items that should not be linked. These 36 are not definitive and may be altered to suit. --]] p.items = { "Australia", "Austria", "Belgium", "Canada", "China", "Denmark", "England", "France", "Germany", "Greece", "Hungary", "Iceland", "India", "Republic of Ireland", "Israel", "Italy", "Jamaica", "Japan", "Luxembourg", "Mexico", "Netherlands", "New Zealand", "Northern Ireland", "Norway", "Poland", "Portugal", "Russia", "Scotland", "South Africa", "Spain", "Sweden", "Switzerland", "Turkey", "United Kingdom", "UK", "United States", "USA", "Wales", } --[[ This provides a convenient way to create a test whether an item is on the list. --]] p.itemsindex = {} for i, v in ipairs(p.items) do p.itemsindex[v] = true end return p os706fxg7ivfr0wq2o2z1afni11n93q 803486 803485 2026-07-30T00:15:03Z SM7 3953 8 revisions imported from [[:en:Module:WikidataIB/nolinks]] 803485 Scribunto text/plain local p ={} --[[ The values here are the English sitelinks for items that should not be linked. These 36 are not definitive and may be altered to suit. --]] p.items = { "Australia", "Austria", "Belgium", "Canada", "China", "Denmark", "England", "France", "Germany", "Greece", "Hungary", "Iceland", "India", "Republic of Ireland", "Israel", "Italy", "Jamaica", "Japan", "Luxembourg", "Mexico", "Netherlands", "New Zealand", "Northern Ireland", "Norway", "Poland", "Portugal", "Russia", "Scotland", "South Africa", "Spain", "Sweden", "Switzerland", "Turkey", "United Kingdom", "UK", "United States", "USA", "Wales", } --[[ This provides a convenient way to create a test whether an item is on the list. --]] p.itemsindex = {} for i, v in ipairs(p.items) do p.itemsindex[v] = true end return p os706fxg7ivfr0wq2o2z1afni11n93q Module:WikidataIB/titleformats 828 101356 803487 2019-09-15T16:39:21Z en>RexxS 0 Creating Module:WikidataIB/titleformats 803487 Scribunto text/plain --[[ To satisfy Wikipedia:Manual of Style/Titles, certain types of items are italicised, and others are quoted. This submodule lists the entity-ids used in 'instance of' (P31), which allows a module to identify the values that should be formatted. The table p.formats is indexed by entity-id, and contains the value " or '' --]] local p = {} p.italics = { Q571, -- book Q13593966, -- literary trilogy Q277759, -- book series Q2188189, -- musical work Q11424, -- film Q13593818, -- film trilogy Q24856, -- film series Q5398426, -- television series Q482994, -- album Q169930, -- extended play Q1760610, -- comic book Q7889, -- video game Q7058673, -- video game series Q25379, -- play Q2743, -- musical Q37484, -- epic poem } p.quotes = { Q207628, -- musical composition } p.formats = {} for idx, val in ipairs(p.italics) do p.formats[val] = '"' end for idx, val in ipairs(p.quotes) do p.formats[val] = "''" end return p 9wfj48pnl0jtmf6yr189wo4gevlnbbz 803488 803487 2019-09-15T17:31:59Z en>RexxS 0 right way round 803488 Scribunto text/plain --[[ To satisfy Wikipedia:Manual of Style/Titles, certain types of items are italicised, and others are quoted. This submodule lists the entity-ids used in 'instance of' (P31), which allows a module to identify the values that should be formatted. The table p.formats is indexed by entity-id, and contains the value " or '' --]] local p = {} p.italics = { Q571, -- book Q13593966, -- literary trilogy Q277759, -- book series Q2188189, -- musical work Q11424, -- film Q13593818, -- film trilogy Q24856, -- film series Q5398426, -- television series Q482994, -- album Q169930, -- extended play Q1760610, -- comic book Q7889, -- video game Q7058673, -- video game series Q25379, -- play Q2743, -- musical Q37484, -- epic poem } p.quotes = { Q207628, -- musical composition } p.formats = {} for idx, val in ipairs(p.italics) do p.formats[val] = "''" end for idx, val in ipairs(p.quotes) do p.formats[val] = '"' end return p b05fxq9kaj3vbgor458s2m9pavbtbd6 803489 803488 2019-09-15T17:58:06Z en>RexxS 0 return size of formats table 803489 Scribunto text/plain --[[ To satisfy Wikipedia:Manual of Style/Titles, certain types of items are italicised, and others are quoted. This submodule lists the entity-ids used in 'instance of' (P31), which allows a module to identify the values that should be formatted. The table p.formats is indexed by entity-id, and contains the value " or '' --]] local p = {} p.italics = { Q571, -- book Q13593966, -- literary trilogy Q277759, -- book series Q2188189, -- musical work Q11424, -- film Q13593818, -- film trilogy Q24856, -- film series Q5398426, -- television series Q482994, -- album Q169930, -- extended play Q1760610, -- comic book Q7889, -- video game Q7058673, -- video game series Q25379, -- play Q2743, -- musical Q37484, -- epic poem } p.quotes = { Q207628, -- musical composition } p.size = 0 p.formats = {} for i, v in ipairs(p.italics) do p.formats[v] = "''" p.size = p.size + 1 end for i, v in ipairs(p.quotes) do p.formats[v] = '"' p.size = p.size + 1 end return p i0fwqntli62pdtn0w2c5bvigcwsncma 803490 803489 2019-09-15T18:04:45Z en>RexxS 0 strings 803490 Scribunto text/plain --[[ To satisfy Wikipedia:Manual of Style/Titles, certain types of items are italicised, and others are quoted. This submodule lists the entity-ids used in 'instance of' (P31), which allows a module to identify the values that should be formatted. The table p.formats is indexed by entity-id, and contains the value " or '' --]] local p = {} p.italics = { "Q571", -- book "Q13593966", -- literary trilogy "Q277759", -- book series "Q2188189", -- musical work "Q11424", -- film "Q13593818", -- film trilogy "Q24856", -- film series "Q5398426", -- television series "Q482994", -- album "Q169930", -- extended play "Q1760610", -- comic book "Q7889", -- video game "Q7058673", -- video game series "Q25379", -- play "Q2743", -- musical "Q37484", -- epic poem } p.quotes = { "Q207628", -- musical composition } p.size = 0 p.formats = {} for i, v in ipairs(p.italics) do p.formats[v] = "''" p.size = p.size + 1 end for i, v in ipairs(p.quotes) do p.formats[v] = '"' p.size = p.size + 1 end return p ceo95yqn1m1wfaizrph32yqgl164ul4 803491 803490 2019-09-25T18:01:12Z en>MusikBot II 0 Protected "[[Module:WikidataIB/titleformats]]": [[Wikipedia:High-risk templates|High-risk template or module]] ([[User:MusikBot II/TemplateProtector|more info]]) ([Edit=Require template editor access] (indefinite) [Move=Require template editor access] (indefinite)) 803490 Scribunto text/plain --[[ To satisfy Wikipedia:Manual of Style/Titles, certain types of items are italicised, and others are quoted. This submodule lists the entity-ids used in 'instance of' (P31), which allows a module to identify the values that should be formatted. The table p.formats is indexed by entity-id, and contains the value " or '' --]] local p = {} p.italics = { "Q571", -- book "Q13593966", -- literary trilogy "Q277759", -- book series "Q2188189", -- musical work "Q11424", -- film "Q13593818", -- film trilogy "Q24856", -- film series "Q5398426", -- television series "Q482994", -- album "Q169930", -- extended play "Q1760610", -- comic book "Q7889", -- video game "Q7058673", -- video game series "Q25379", -- play "Q2743", -- musical "Q37484", -- epic poem } p.quotes = { "Q207628", -- musical composition } p.size = 0 p.formats = {} for i, v in ipairs(p.italics) do p.formats[v] = "''" p.size = p.size + 1 end for i, v in ipairs(p.quotes) do p.formats[v] = '"' p.size = p.size + 1 end return p ceo95yqn1m1wfaizrph32yqgl164ul4 803492 803491 2020-04-30T16:11:19Z en>RexxS 0 add magazine 803492 Scribunto text/plain --[[ To satisfy Wikipedia:Manual of Style/Titles, certain types of items are italicised, and others are quoted. This submodule lists the entity-ids used in 'instance of' (P31), which allows a module to identify the values that should be formatted. The table p.formats is indexed by entity-id, and contains the value " or '' --]] local p = {} p.italics = { "Q571", -- book "Q13593966", -- literary trilogy "Q277759", -- book series "Q2188189", -- musical work "Q11424", -- film "Q13593818", -- film trilogy "Q24856", -- film series "Q5398426", -- television series "Q482994", -- album "Q169930", -- extended play "Q1760610", -- comic book "Q7889", -- video game "Q7058673", -- video game series "Q25379", -- play "Q2743", -- musical "Q37484", -- epic poem "Q41298", -- magazine } p.quotes = { "Q207628", -- musical composition } p.size = 0 p.formats = {} for i, v in ipairs(p.italics) do p.formats[v] = "''" p.size = p.size + 1 end for i, v in ipairs(p.quotes) do p.formats[v] = '"' p.size = p.size + 1 end return p kf27lmax414v7k61jzza3jomkstxn1m 803493 803492 2026-07-30T00:15:17Z SM7 3953 6 revisions imported from [[:en:Module:WikidataIB/titleformats]] 803492 Scribunto text/plain --[[ To satisfy Wikipedia:Manual of Style/Titles, certain types of items are italicised, and others are quoted. This submodule lists the entity-ids used in 'instance of' (P31), which allows a module to identify the values that should be formatted. The table p.formats is indexed by entity-id, and contains the value " or '' --]] local p = {} p.italics = { "Q571", -- book "Q13593966", -- literary trilogy "Q277759", -- book series "Q2188189", -- musical work "Q11424", -- film "Q13593818", -- film trilogy "Q24856", -- film series "Q5398426", -- television series "Q482994", -- album "Q169930", -- extended play "Q1760610", -- comic book "Q7889", -- video game "Q7058673", -- video game series "Q25379", -- play "Q2743", -- musical "Q37484", -- epic poem "Q41298", -- magazine } p.quotes = { "Q207628", -- musical composition } p.size = 0 p.formats = {} for i, v in ipairs(p.italics) do p.formats[v] = "''" p.size = p.size + 1 end for i, v in ipairs(p.quotes) do p.formats[v] = '"' p.size = p.size + 1 end return p kf27lmax414v7k61jzza3jomkstxn1m बोरिस जोर्जेविच परचात्किन 0 101357 803655 2026-07-30T06:42:06Z Asorev 38000 पन्ना बनावल गइल "{{Infobox person | name = बोरिस परचात्किन | native_name = Борис Георгиевич Перчаткин | image = | caption = | birth_date = {{Birth date and age|1946|7|1}} | birth_place = [[मिर्होरौद]], यूक्रेनी सोवियत समाजवादी गणराज्य|यूक्रेनी..." के साथ 803655 wikitext text/x-wiki {{Infobox person | name = बोरिस परचात्किन | native_name = Борис Георгиевич Перчаткин | image = | caption = | birth_date = {{Birth date and age|1946|7|1}} | birth_place = [[मिर्होरौद]], [[यूक्रेनी सोवियत समाजवादी गणराज्य|यूक्रेनी SSR]], [[सोवियत संघ]]<ref name="ostrovskaja_68">{{cite journal |last=Островская |first=О. П. |year=2017 |title=Судьбы участников эмиграционного движения пятидесятников на дальнем востоке в 1970-1980-х гг. |journal=Владивосток – точка возвращения: прошлое и настоящее русской эмиграции |publisher=Дальневосточный юридический институт МВД РФ |url=http://dot.kostacademy.kz/bible/files/769476832.pdf#page=65 |language=ru |pages=68}}</ref> | occupation = मानवाधिकार कार्यकर्ता, धार्मिक नेता | children = 6<ref name="ostrovskaja_68" /> }} '''बोरिस जोर्जेविच परचात्किन''' (रूसी: ''Борис Георгиевич Перчаткин''; जनम: 1 जुलाई 1946) सोवियत संघ आ अमेरिका के एकठु मानवाधिकार कार्यकर्ता हउवँ आ 1970 के दशक के अंत आ 1980 के दशक के शुरुआत में [[नाखोदका]] शहर से शुरू भइल धार्मिक प्रवास आंदोलन के प्रमुख नेता रहलें<ref name="ostrovskaja_68" />। 1989 में उहाँ [[अमेरिका|संयुक्त राज्य अमेरिका]] में "लाउतेनबर्ग संशोधन" (Lautenberg Amendment) के पास करावे खातिर पैरवी के नेतृत्व कइलीं, जेकरा चलते पूर्व सोवियत संघ के देसवन से लगभग 10 लाख धार्मिक अल्पसंख्यकलोग कानूनी रूप से अमेरिका में जा के बस सकल<ref name="romanovic">{{cite web |last=Романович |first=Михаил |year=2023 |title=Правозащитник, Борис Перчаткин, смертельно болен |website=spasenie.by |date=2023-09-26 |url=https://spasenie.by/publ/pravozashhitnik_boris_perchatkin_smertelno_bolen/4-1-0-82 |language=ru}}</ref>। == जीवनी == === सुरुआती जीवन आ चर्च में शामिल भइल === बोरिस परचात्किन के जनम एक सैन्य पायलट के परिवार में भइल रहे। बोरिस के जनम के कुछ समय बाद ही उनकर पिता के एक कार दुर्घटना में मौत हो गइल। कुछ साल बाद उनकर माई दूसरा सैन्यकर्मी से बियाह क लेली। लेकिन 1953 में उनकर सौतेला बाप के विद्रोह के दौरान हुक्म ना माने के कारण गिरफ्तार क लिहल गइल आ [[वोरकुता]] के जबरन श्रम शिविर में 25 साल के सजा सुनावल गइल<ref name="ostrovskaja_68" />। 16 साल के उमिर में बोरिस नाखोदका में पेन्टेकोस्टल चर्च के खोज कइलें आ ओकर सदस्य बन गइलें। ओकरा बाद उहाँ सोवियत संघ में धार्मिक लोगन पर भइल अत्याचार के बारे में जानकारी जुटावे लगलें आ बिदेसी पत्रकारन से शेयर करे लगलें<ref name="semjonova">{{cite web |last=Семёнова |first=Виктория |year=2020 |title=Борис Перчаткин — о том, как помогал верующим из СССР попасть в США |website=usa.one |date=2020-01-03 |url=https://usa.one/2020/01/boris-perchatkin-o-veruyushhix-v-sssr-immigracii-v-ssha-i-aktivizme/ |language=ru}}</ref>। === प्रवास आंदोलन आ असंतुष्टन से संबंध === 1976 में परचात्किन अपने साथी वासिली पात्रुशेव, वी. ए. बुरलाचेन्को आ व्लादिमीर स्टेपानोव के साथे [[मॉस्को]] में सोवियत असंतुष्टन (dissidents) से संपर्क साधलें<ref name="ostrovskaja_68_69">{{cite journal |last=Островская |first=О. П. |year=2017 |title=Судьбы участников эмиграционного движения пятидесятников на дальнем востоке в 1970-1980-х гг. |journal=Владивосток – точка возвращения: прошлое и настоящее русской эмиграции |publisher=Дальневосточный юридический институт МВД РФ |url=http://dot.kostacademy.kz/bible/files/769476832.pdf#page=65 |language=ru |pages=68—69}}</ref>। असंतुष्टलोग सलाह दिहल कि बढ़त प्रवास आंदोलन के एक संगठित रूप दिहल जाव<ref name="ostrovskaja_68_69" />। ओही साल [[मॉस्को हेलसिंकी ग्रुप]] के प्रतिनिधि एल. वी. वोरोनिना नाखोदका आइली<ref name="ostrovskaja_68_69" />। ओकरा बाद से परचात्किन पेन्टेकोस्टल समुदाय के सोवियत संघ से सामूहिक निकास खातिर सक्रिय रूप से काम कइलें<ref name="ostrovskaja_68_69" />। उहाँ [[संयुक्त राष्ट्र]], [[चर्चन के विश्व परिषद]] (WCC) आ [[हेलसिंकी समझौता]] पर दस्तखत करे वाला देसन के प्रमुखन के अपील भेज के सोवियत संघ में धार्मिक उत्पीड़न के जानकारी दिहलीं<ref name="nashaamerica">{{cite web|url=http://www.nashaamerica.com/blogi/chtivo/item-894/|title=Он спас 1 млн человек. Сегодня они не могут собрать ему на операцию. Как же так?|website=www.nashaamerica.com|date=2023-09-26|access-date=2023-09-26|language=ru|archive-date=2023-09-26|archive-url=https://web.archive.org/web/20230926133215/http://www.nashaamerica.com/blogi/chtivo/item-894/|url-status=live}}</ref>। 1977 में निकोलाई गोरेती के साथे पेन्टेकोस्टल समुदाय के ओर से गिरफ्तार असंतुष्टन [[अलेक्जेंडर गिंज़बर्ग]], [[युरी ओर्लोव]] आ [[अनातोली शचरान्स्की]] के बचाव कइलें<ref name="ostrovskaja_66">{{cite journal |last=Островская |first=О. П. |year=2017 |title=Судьбы участников эмиграционного движения пятидесятников на дальнем востоке в 1970-1980-х гг. |journal=Владивосток – точка возвращения: прошлое и настоящее русской эмиграции |publisher=Дальневосточный юридический институт МВД РФ |url=http://dot.kostacademy.kz/bible/files/769476832.pdf#page=65 |language=ru |pages=66}}</ref>। ओही साल बाकी ईसाई पंथन के साथ मिलकर उत्पीड़ित ईसाइयन खातिर देश छोड़े के इजाजत माँगे खातिर सार्वजनिक भूख हड़ताल में भाग लिहलें<ref name="ukrweekly">{{cite journal|author=Zenon Snylyk|title=Dissidents fast across USSR|url=https://archive.ukrweekly.com/print-media/1977/The_Ukrainian_Weekly_1977-38.pdf|journal=The Ukrainian Weekly|publisher=Ukrainian National Association|date=1977-10-09|volume=LXXXIV|issue=222|pages=2|language=en|archive-date=2023-09-26|archive-url=https://web.archive.org/web/20230926074510/https://archive.ukrweekly.com/print-media/1977/The_Ukrainian_Weekly_1977-38.pdf|url-status=live}}</ref>। 20 दिसंबर 1978 के परचात्किन नाखोदका के डाकघर से अमेरिकी राष्ट्रपति [[जिमी कार्टर]] के नया साल के टेलीग्राम भेजे के कोशिश कइलें, जेमे धार्मिक आजादी से वंचित लोगन पर ध्यान देवे के बिनती कइल गइल रहे<ref name="ostrovskaja_69">{{cite journal |last=Островская |first=О. П. |year=2017 |title=Судьбы участников эмиграционного движения пятидесятников на дальнем востоке в 1970-1980-х гг. |journal=Владивосток – точка возвращения: прошлое и настоящее русской эмиграции |publisher=Дальневосточный юридический институт МВД РФ |url=http://dot.kostacademy.kz/bible/files/769476832.pdf#page=65 |language=ru |pages=69}}</ref><ref name="nashaamerica"/><ref name="dementjev">{{cite book |author=Андрей Дементьев |title=АВЕН-ЕЗЕР. Евангельское движение в Приморье 1898-1990 годы |url=http://www.rusbaptist.stunda.org/aven-ezer-primorje.pdf |location=Vladivostok |publisher=Русский остров |year=2011 |pages=213 |isbn=978-5-93577-054-2 |language=ru |archive-date=2023-09-25 |archive-url=https://web.archive.org/web/20230925212100/http://www.rusbaptist.stunda.org/aven-ezer-primorje.pdf |url-status=live}}</ref>। 1979 से उहाँ देस छोड़े के इच्छुक लोगन खातिर एक केंद्र बनाए के संगठनात्मक काम के नेतृत्व कइलें। 1980 में उहाँ के नवगठित "सोवियत पेन्टेकोस्टल चर्च काउंसिल फॉर इमिग्रेशन" के सचिव चुनल गइल<ref name="ostrovskaja_69" />। === सरकारी उत्पीड़न आ जेल === प्रवास समिति के सबसे सक्रिय सदस्य भइला के कारण उहाँ सोवियत अधिकारियों के निशाना बन गइलें<ref name="ostrovskaja_69" />। स्थानीय मीडिया में उनुका आ उनकर परिवार के खिलाफ बदनामी के अभियान चलावल गइल<ref name="ostrovskaja_69" />। 21 अगस्त 1980 के परचात्किन के नाखोदका में गिरफ्तार क लिहल गइल<ref name="ostrovskaja_69" />। मार्च 1981 के अंत में<ref name="dtic">[https://apps.dtic.mil/sti/pdfs/ADA145453.pdf Chronology of Soviet Dissidence: January 1970 through December 1982]</ref>, आरएसएफएसआर आपराधिक संहिता के अनुच्छेद 190.1 ("सोवियत राज्य आ सामाजिक व्यवस्था के बदनाम करे वाला झूठ फइलावे") के तहत उनुका के 2 साल के जेल के सजा सुनावल गइल<ref name="dtic"/>। [[वॉइस ऑफ अमेरिका]] के रिपोर्ट के मुताबिक, पूछताछ आ ट्रायल के दौरान उनुका के न्यूरोलेप्टिक रसायन दिहल गइल जेकरा से उहाँ कई बेर बेहोश हो गइलें<ref name="ostrovskaja_69" />। अगस्त 1982 में रिहा भइला के बाद उहाँ देश छोड़े के अधिकार खातिर लड़ाई जारी रखलें। लेकिन फरवरी 1983 में उनुका के दोबारा गिरफ्तार क लिहल गइल आ अवैध हथियार रखे के झूठा आरोप में 1.5 साल के जेल के सजा दिहल गइल<ref name="ostrovskaja_69" />। === अमेरिका में प्रवास आ लाउतेनबर्ग संशोधन === 1987 में उहाँ अमेरिकी कांग्रेस खातिर "सोवियत संघ में धर्म के स्थिति" पर एक रिपोर्ट तैयार कइलें। ई रिपोर्ट कांग्रेस में पढ़ल गइल आ परचात्किन के अमेरिका के विदेश मंत्री [[जॉर्ज शुल्त्ज़]]<ref name="romanovic"/> आ मुख्य न्यायाधीश [[विलियम रेनक्विस्ट]]<ref name="romanovic"/> से मिले खातिर बुलावल गइल। मई 1988 में जब अमेरिकी राष्ट्रपति [[रोनाल्ड रीगन]] आ सोवियत नेता [[मिखाइल गोर्बाचेव]] मॉस्को में शिखर सम्मेलन कइलें, तब परचात्किन के सोवियत प्रोटेस्टेंट प्रतिनिधि के रूप में फर्स्ट लेडी [[नैंसी रीगन]] से मिले खातिर 40 लोगन के प्रतिनिधिमंडल में आमंत्रित कइल गइल रहे<ref name="gura_173">{{cite journal |last=Гура |first=В. О. |year=2018 |title=Історія та есхатологічні мотиви релігійної еміграції пізніх протестантів із СРСР 1987–2017 рр. |journal=Гілея: науковий вісник |issue=135 |pages=171—175 |issn=2076-1554 |url=http://www.irbis-nbuv.gov.ua/cgi-bin/irbis_nbuv/cgiirbis_64.exe?C21COM=2&I21DBN=UJRN&P21DBN=UJRN&IMAGE_FILE_DOWNLOAD=1&Image_file_name=PDF/gileya_2018_135_47.pdf |language=uk}}</ref>। ओह बैठक में उहाँ धार्मिक आजादी आ बिना कौनों बाधा के देस छोड़े के अधिकार खातिर अपील सउंपलें। 26 जुलाई 1988 के परचात्किन अपना पूरा परिवार के साथ अमेरिका चले गइलें<ref name="gura_173" /><ref name="ostrovskaja_69" />। 4 अगस्त 1988 के उहाँ अमेरिकी कांग्रेस में सोवियत संघ में धार्मिक उत्पीड़न पर भाषण दिहले<ref name="gura_173" /><ref name="ostrovskaja_69" /><ref name="photo">{{cite web|url=https://russiainphoto.ru/photos/195013/|title=Foto - Boris Perchatkin|website=russiainphoto.ru|date=2023-09-26|language=ru}}</ref>। एही प्रयासन के बदौलत<ref name="futornyj">{{cite journal|author=Виталий Футорный|title=25-летие начала исхода|url=https://issuu.com/afisha/docs/diaspora_08-11-13/9|journal=Диаспора. Славянская общественная газета|date=2013-08-11|volume=16|issue=15|pages=9|language=ru}}</ref> 1989 में "लाउतेनबर्ग संशोधन" पास भइल, जेकर नाम [[न्यू जर्सी]] के सीनेटर [[फ्रैंक लाउतेनबर्ग]] के नाम पर रखल गइल रहे। एह कानून के पहिला 10 साल में लगभग 3,50,000 धार्मिक शरणार्थी अमेरिका में बसलें<ref name="gura_173" /><ref name="semjonova" />, आ 2010 तक ई संख्या लगभग 10 लाख तक पहुँच गइल<ref name="romanovic"/>। === अमेरिका में मानवाधिकार गतिविधि === अमेरिका में रहत समय परचात्किन अपना जेल आ संघर्ष के जीवन पर एक वेबसाइट शुरू कइलें आ एक ईसाई मानवाधिकार संगठन ARRC (American-Russian Relief Committee) के स्थापना कइलें<ref name="ostrovskaja_69" /><ref name="romanovic"/><ref name="arrc">{{cite book|author=United States Congress Commission on Security and Cooperation in Europe|title=Unregistered Religious Groups in Russia: Hearing Before the Commission on Security and Cooperation in Europe|url=https://books.google.com/books?id=_YhIiVBx_5kC|year=2007|publisher=U.S. Government Printing Office|isbn=978-0-16-077910-7|language=en}}</ref>। उहाँ अपना आत्मकथात्मक किताब "आग के रास्ते" (रूसी: ''Огненные тропы'') प्रकाशित कइलें<ref name="ostrovskaja_69" />। 2025 के गरमी में परचात्किन आ उनकर समर्थक अमेरिकी विदेश विभाग आ अंतर्राष्ट्रीय धार्मिक स्वतंत्रता पर अमेरिकी आयोग (USCIRF) के सामने गवाही दिहलें, जेमे यूक्रेन आ रूस में शांतिवादी ईसाइयन के उत्पीड़न आ जबरन सेना में भर्ती के मुद्दा उठावल गइल। एह सिलसिले में उहाँ [[Uniting for Ukraine|U4U]] कार्यक्रम के तहत अमेरिकी आवे वाला यूक्रेनी शांतिवादी ईसाइयन खातिर विशेष कोटा खातिर पैरवी कइलें<ref name="slavicsac">{{cite web|url=https://www.slavicsac.com/2025/07/14/ukraine-russian-christians/|title=В США обсуждают защиту христиан из Украины и России|author=SlavicSac|website=Slavic Sacramento|date=2025-07-15|language=ru}}</ref>। == सम्मान आ उल्लेख == प्रसिद्ध सोवियत असंतुष्ट [[आंद्रेई साखारोव]] 1981 में लिखे गइल अपना निबंध "वैज्ञानिकन के जिम्मेदारी" में राजनीतिक बंदियों के सूची में बोरिस परचात्किन के नाम के विशेष रूप से उल्लेख कइले रहलें<ref name="sakharov">{{cite web|url=https://www.sakharov.space/lib/otvetstvennost-uchenyh|title=Сахаров. Ответственность учёных|website=sakharov.space|language=ru}}</ref>। == संदर्भ == <references /> == बाहरी कड़ी == * {{Cite journal |author = Michael Rowe |title = The Soviet pentecostal emigration movement |url = https://www.tandfonline.com/doi/epdf/10.1080/09637498308431098 |journal = Religion in Communist Lands |language = en |date = 2 January 2008 |volume = 11 |issue = 3 |pages = 337–339 |doi = 10.1080/09637498308431098 |ref = Rowe |url-access= subscription }} * {{Cite journal |author = Catherine Wanner |title = Religion and Refugee Resettlement: Evolving Connections to Ukraine since World War II |url = https://brill.com/view/journals/css/44/1-2/article-p44_4.xml |journal = Canadian-American Slavic Studies |language = en |publisher = Brill Schöningh |date = January 2010 |volume = 44 |issue = 1–2 |pages = 44–66 |issn = 2210-2396 |doi = 10.1163/221023910X512796 |ref = Wanner |url-access= subscription}} * {{Cite news|title=Soviet Pentecostals Meet Illegally and Form Council|last=Special To the New York Times|url=https://www.nytimes.com/1979/09/05/archives/soviet-pentecostals-meet-illegally-and-form-council-20-delegates.html|access-date=2025-12-18|lang=en|archive-url=https://web.archive.org/web/20180310060316/https://www.nytimes.com/1979/09/05/archives/soviet-pentecostals-meet-illegally-and-form-council-20-delegates.html|archive-date=2018-03-10}} [[श्रेणी:जीवित लोग]] a1vg1bchqoun06ikl4wrbsvl2pzyuhh 803656 803655 2026-07-30T06:44:27Z Asorev 38000 /* बाहरी कड़ी */ 803656 wikitext text/x-wiki {{Infobox person | name = बोरिस परचात्किन | native_name = Борис Георгиевич Перчаткин | image = | caption = | birth_date = {{Birth date and age|1946|7|1}} | birth_place = [[मिर्होरौद]], [[यूक्रेनी सोवियत समाजवादी गणराज्य|यूक्रेनी SSR]], [[सोवियत संघ]]<ref name="ostrovskaja_68">{{cite journal |last=Островская |first=О. П. |year=2017 |title=Судьбы участников эмиграционного движения пятидесятников на дальнем востоке в 1970-1980-х гг. |journal=Владивосток – точка возвращения: прошлое и настоящее русской эмиграции |publisher=Дальневосточный юридический институт МВД РФ |url=http://dot.kostacademy.kz/bible/files/769476832.pdf#page=65 |language=ru |pages=68}}</ref> | occupation = मानवाधिकार कार्यकर्ता, धार्मिक नेता | children = 6<ref name="ostrovskaja_68" /> }} '''बोरिस जोर्जेविच परचात्किन''' (रूसी: ''Борис Георгиевич Перчаткин''; जनम: 1 जुलाई 1946) सोवियत संघ आ अमेरिका के एकठु मानवाधिकार कार्यकर्ता हउवँ आ 1970 के दशक के अंत आ 1980 के दशक के शुरुआत में [[नाखोदका]] शहर से शुरू भइल धार्मिक प्रवास आंदोलन के प्रमुख नेता रहलें<ref name="ostrovskaja_68" />। 1989 में उहाँ [[अमेरिका|संयुक्त राज्य अमेरिका]] में "लाउतेनबर्ग संशोधन" (Lautenberg Amendment) के पास करावे खातिर पैरवी के नेतृत्व कइलीं, जेकरा चलते पूर्व सोवियत संघ के देसवन से लगभग 10 लाख धार्मिक अल्पसंख्यकलोग कानूनी रूप से अमेरिका में जा के बस सकल<ref name="romanovic">{{cite web |last=Романович |first=Михаил |year=2023 |title=Правозащитник, Борис Перчаткин, смертельно болен |website=spasenie.by |date=2023-09-26 |url=https://spasenie.by/publ/pravozashhitnik_boris_perchatkin_smertelno_bolen/4-1-0-82 |language=ru}}</ref>। == जीवनी == === सुरुआती जीवन आ चर्च में शामिल भइल === बोरिस परचात्किन के जनम एक सैन्य पायलट के परिवार में भइल रहे। बोरिस के जनम के कुछ समय बाद ही उनकर पिता के एक कार दुर्घटना में मौत हो गइल। कुछ साल बाद उनकर माई दूसरा सैन्यकर्मी से बियाह क लेली। लेकिन 1953 में उनकर सौतेला बाप के विद्रोह के दौरान हुक्म ना माने के कारण गिरफ्तार क लिहल गइल आ [[वोरकुता]] के जबरन श्रम शिविर में 25 साल के सजा सुनावल गइल<ref name="ostrovskaja_68" />। 16 साल के उमिर में बोरिस नाखोदका में पेन्टेकोस्टल चर्च के खोज कइलें आ ओकर सदस्य बन गइलें। ओकरा बाद उहाँ सोवियत संघ में धार्मिक लोगन पर भइल अत्याचार के बारे में जानकारी जुटावे लगलें आ बिदेसी पत्रकारन से शेयर करे लगलें<ref name="semjonova">{{cite web |last=Семёнова |first=Виктория |year=2020 |title=Борис Перчаткин — о том, как помогал верующим из СССР попасть в США |website=usa.one |date=2020-01-03 |url=https://usa.one/2020/01/boris-perchatkin-o-veruyushhix-v-sssr-immigracii-v-ssha-i-aktivizme/ |language=ru}}</ref>। === प्रवास आंदोलन आ असंतुष्टन से संबंध === 1976 में परचात्किन अपने साथी वासिली पात्रुशेव, वी. ए. बुरलाचेन्को आ व्लादिमीर स्टेपानोव के साथे [[मॉस्को]] में सोवियत असंतुष्टन (dissidents) से संपर्क साधलें<ref name="ostrovskaja_68_69">{{cite journal |last=Островская |first=О. П. |year=2017 |title=Судьбы участников эмиграционного движения пятидесятников на дальнем востоке в 1970-1980-х гг. |journal=Владивосток – точка возвращения: прошлое и настоящее русской эмиграции |publisher=Дальневосточный юридический институт МВД РФ |url=http://dot.kostacademy.kz/bible/files/769476832.pdf#page=65 |language=ru |pages=68—69}}</ref>। असंतुष्टलोग सलाह दिहल कि बढ़त प्रवास आंदोलन के एक संगठित रूप दिहल जाव<ref name="ostrovskaja_68_69" />। ओही साल [[मॉस्को हेलसिंकी ग्रुप]] के प्रतिनिधि एल. वी. वोरोनिना नाखोदका आइली<ref name="ostrovskaja_68_69" />। ओकरा बाद से परचात्किन पेन्टेकोस्टल समुदाय के सोवियत संघ से सामूहिक निकास खातिर सक्रिय रूप से काम कइलें<ref name="ostrovskaja_68_69" />। उहाँ [[संयुक्त राष्ट्र]], [[चर्चन के विश्व परिषद]] (WCC) आ [[हेलसिंकी समझौता]] पर दस्तखत करे वाला देसन के प्रमुखन के अपील भेज के सोवियत संघ में धार्मिक उत्पीड़न के जानकारी दिहलीं<ref name="nashaamerica">{{cite web|url=http://www.nashaamerica.com/blogi/chtivo/item-894/|title=Он спас 1 млн человек. Сегодня они не могут собрать ему на операцию. Как же так?|website=www.nashaamerica.com|date=2023-09-26|access-date=2023-09-26|language=ru|archive-date=2023-09-26|archive-url=https://web.archive.org/web/20230926133215/http://www.nashaamerica.com/blogi/chtivo/item-894/|url-status=live}}</ref>। 1977 में निकोलाई गोरेती के साथे पेन्टेकोस्टल समुदाय के ओर से गिरफ्तार असंतुष्टन [[अलेक्जेंडर गिंज़बर्ग]], [[युरी ओर्लोव]] आ [[अनातोली शचरान्स्की]] के बचाव कइलें<ref name="ostrovskaja_66">{{cite journal |last=Островская |first=О. П. |year=2017 |title=Судьбы участников эмиграционного движения пятидесятников на дальнем востоке в 1970-1980-х гг. |journal=Владивосток – точка возвращения: прошлое и настоящее русской эмиграции |publisher=Дальневосточный юридический институт МВД РФ |url=http://dot.kostacademy.kz/bible/files/769476832.pdf#page=65 |language=ru |pages=66}}</ref>। ओही साल बाकी ईसाई पंथन के साथ मिलकर उत्पीड़ित ईसाइयन खातिर देश छोड़े के इजाजत माँगे खातिर सार्वजनिक भूख हड़ताल में भाग लिहलें<ref name="ukrweekly">{{cite journal|author=Zenon Snylyk|title=Dissidents fast across USSR|url=https://archive.ukrweekly.com/print-media/1977/The_Ukrainian_Weekly_1977-38.pdf|journal=The Ukrainian Weekly|publisher=Ukrainian National Association|date=1977-10-09|volume=LXXXIV|issue=222|pages=2|language=en|archive-date=2023-09-26|archive-url=https://web.archive.org/web/20230926074510/https://archive.ukrweekly.com/print-media/1977/The_Ukrainian_Weekly_1977-38.pdf|url-status=live}}</ref>। 20 दिसंबर 1978 के परचात्किन नाखोदका के डाकघर से अमेरिकी राष्ट्रपति [[जिमी कार्टर]] के नया साल के टेलीग्राम भेजे के कोशिश कइलें, जेमे धार्मिक आजादी से वंचित लोगन पर ध्यान देवे के बिनती कइल गइल रहे<ref name="ostrovskaja_69">{{cite journal |last=Островская |first=О. П. |year=2017 |title=Судьбы участников эмиграционного движения пятидесятников на дальнем востоке в 1970-1980-х гг. |journal=Владивосток – точка возвращения: прошлое и настоящее русской эмиграции |publisher=Дальневосточный юридический институт МВД РФ |url=http://dot.kostacademy.kz/bible/files/769476832.pdf#page=65 |language=ru |pages=69}}</ref><ref name="nashaamerica"/><ref name="dementjev">{{cite book |author=Андрей Дементьев |title=АВЕН-ЕЗЕР. Евангельское движение в Приморье 1898-1990 годы |url=http://www.rusbaptist.stunda.org/aven-ezer-primorje.pdf |location=Vladivostok |publisher=Русский остров |year=2011 |pages=213 |isbn=978-5-93577-054-2 |language=ru |archive-date=2023-09-25 |archive-url=https://web.archive.org/web/20230925212100/http://www.rusbaptist.stunda.org/aven-ezer-primorje.pdf |url-status=live}}</ref>। 1979 से उहाँ देस छोड़े के इच्छुक लोगन खातिर एक केंद्र बनाए के संगठनात्मक काम के नेतृत्व कइलें। 1980 में उहाँ के नवगठित "सोवियत पेन्टेकोस्टल चर्च काउंसिल फॉर इमिग्रेशन" के सचिव चुनल गइल<ref name="ostrovskaja_69" />। === सरकारी उत्पीड़न आ जेल === प्रवास समिति के सबसे सक्रिय सदस्य भइला के कारण उहाँ सोवियत अधिकारियों के निशाना बन गइलें<ref name="ostrovskaja_69" />। स्थानीय मीडिया में उनुका आ उनकर परिवार के खिलाफ बदनामी के अभियान चलावल गइल<ref name="ostrovskaja_69" />। 21 अगस्त 1980 के परचात्किन के नाखोदका में गिरफ्तार क लिहल गइल<ref name="ostrovskaja_69" />। मार्च 1981 के अंत में<ref name="dtic">[https://apps.dtic.mil/sti/pdfs/ADA145453.pdf Chronology of Soviet Dissidence: January 1970 through December 1982]</ref>, आरएसएफएसआर आपराधिक संहिता के अनुच्छेद 190.1 ("सोवियत राज्य आ सामाजिक व्यवस्था के बदनाम करे वाला झूठ फइलावे") के तहत उनुका के 2 साल के जेल के सजा सुनावल गइल<ref name="dtic"/>। [[वॉइस ऑफ अमेरिका]] के रिपोर्ट के मुताबिक, पूछताछ आ ट्रायल के दौरान उनुका के न्यूरोलेप्टिक रसायन दिहल गइल जेकरा से उहाँ कई बेर बेहोश हो गइलें<ref name="ostrovskaja_69" />। अगस्त 1982 में रिहा भइला के बाद उहाँ देश छोड़े के अधिकार खातिर लड़ाई जारी रखलें। लेकिन फरवरी 1983 में उनुका के दोबारा गिरफ्तार क लिहल गइल आ अवैध हथियार रखे के झूठा आरोप में 1.5 साल के जेल के सजा दिहल गइल<ref name="ostrovskaja_69" />। === अमेरिका में प्रवास आ लाउतेनबर्ग संशोधन === 1987 में उहाँ अमेरिकी कांग्रेस खातिर "सोवियत संघ में धर्म के स्थिति" पर एक रिपोर्ट तैयार कइलें। ई रिपोर्ट कांग्रेस में पढ़ल गइल आ परचात्किन के अमेरिका के विदेश मंत्री [[जॉर्ज शुल्त्ज़]]<ref name="romanovic"/> आ मुख्य न्यायाधीश [[विलियम रेनक्विस्ट]]<ref name="romanovic"/> से मिले खातिर बुलावल गइल। मई 1988 में जब अमेरिकी राष्ट्रपति [[रोनाल्ड रीगन]] आ सोवियत नेता [[मिखाइल गोर्बाचेव]] मॉस्को में शिखर सम्मेलन कइलें, तब परचात्किन के सोवियत प्रोटेस्टेंट प्रतिनिधि के रूप में फर्स्ट लेडी [[नैंसी रीगन]] से मिले खातिर 40 लोगन के प्रतिनिधिमंडल में आमंत्रित कइल गइल रहे<ref name="gura_173">{{cite journal |last=Гура |first=В. О. |year=2018 |title=Історія та есхатологічні мотиви релігійної еміграції пізніх протестантів із СРСР 1987–2017 рр. |journal=Гілея: науковий вісник |issue=135 |pages=171—175 |issn=2076-1554 |url=http://www.irbis-nbuv.gov.ua/cgi-bin/irbis_nbuv/cgiirbis_64.exe?C21COM=2&I21DBN=UJRN&P21DBN=UJRN&IMAGE_FILE_DOWNLOAD=1&Image_file_name=PDF/gileya_2018_135_47.pdf |language=uk}}</ref>। ओह बैठक में उहाँ धार्मिक आजादी आ बिना कौनों बाधा के देस छोड़े के अधिकार खातिर अपील सउंपलें। 26 जुलाई 1988 के परचात्किन अपना पूरा परिवार के साथ अमेरिका चले गइलें<ref name="gura_173" /><ref name="ostrovskaja_69" />। 4 अगस्त 1988 के उहाँ अमेरिकी कांग्रेस में सोवियत संघ में धार्मिक उत्पीड़न पर भाषण दिहले<ref name="gura_173" /><ref name="ostrovskaja_69" /><ref name="photo">{{cite web|url=https://russiainphoto.ru/photos/195013/|title=Foto - Boris Perchatkin|website=russiainphoto.ru|date=2023-09-26|language=ru}}</ref>। एही प्रयासन के बदौलत<ref name="futornyj">{{cite journal|author=Виталий Футорный|title=25-летие начала исхода|url=https://issuu.com/afisha/docs/diaspora_08-11-13/9|journal=Диаспора. Славянская общественная газета|date=2013-08-11|volume=16|issue=15|pages=9|language=ru}}</ref> 1989 में "लाउतेनबर्ग संशोधन" पास भइल, जेकर नाम [[न्यू जर्सी]] के सीनेटर [[फ्रैंक लाउतेनबर्ग]] के नाम पर रखल गइल रहे। एह कानून के पहिला 10 साल में लगभग 3,50,000 धार्मिक शरणार्थी अमेरिका में बसलें<ref name="gura_173" /><ref name="semjonova" />, आ 2010 तक ई संख्या लगभग 10 लाख तक पहुँच गइल<ref name="romanovic"/>। === अमेरिका में मानवाधिकार गतिविधि === अमेरिका में रहत समय परचात्किन अपना जेल आ संघर्ष के जीवन पर एक वेबसाइट शुरू कइलें आ एक ईसाई मानवाधिकार संगठन ARRC (American-Russian Relief Committee) के स्थापना कइलें<ref name="ostrovskaja_69" /><ref name="romanovic"/><ref name="arrc">{{cite book|author=United States Congress Commission on Security and Cooperation in Europe|title=Unregistered Religious Groups in Russia: Hearing Before the Commission on Security and Cooperation in Europe|url=https://books.google.com/books?id=_YhIiVBx_5kC|year=2007|publisher=U.S. Government Printing Office|isbn=978-0-16-077910-7|language=en}}</ref>। उहाँ अपना आत्मकथात्मक किताब "आग के रास्ते" (रूसी: ''Огненные тропы'') प्रकाशित कइलें<ref name="ostrovskaja_69" />। 2025 के गरमी में परचात्किन आ उनकर समर्थक अमेरिकी विदेश विभाग आ अंतर्राष्ट्रीय धार्मिक स्वतंत्रता पर अमेरिकी आयोग (USCIRF) के सामने गवाही दिहलें, जेमे यूक्रेन आ रूस में शांतिवादी ईसाइयन के उत्पीड़न आ जबरन सेना में भर्ती के मुद्दा उठावल गइल। एह सिलसिले में उहाँ [[Uniting for Ukraine|U4U]] कार्यक्रम के तहत अमेरिकी आवे वाला यूक्रेनी शांतिवादी ईसाइयन खातिर विशेष कोटा खातिर पैरवी कइलें<ref name="slavicsac">{{cite web|url=https://www.slavicsac.com/2025/07/14/ukraine-russian-christians/|title=В США обсуждают защиту христиан из Украины и России|author=SlavicSac|website=Slavic Sacramento|date=2025-07-15|language=ru}}</ref>। == सम्मान आ उल्लेख == प्रसिद्ध सोवियत असंतुष्ट [[आंद्रेई साखारोव]] 1981 में लिखे गइल अपना निबंध "वैज्ञानिकन के जिम्मेदारी" में राजनीतिक बंदियों के सूची में बोरिस परचात्किन के नाम के विशेष रूप से उल्लेख कइले रहलें<ref name="sakharov">{{cite web|url=https://www.sakharov.space/lib/otvetstvennost-uchenyh|title=Сахаров. Ответственность учёных|website=sakharov.space|language=ru}}</ref>। == संदर्भ == <references /> == बाहरी कड़ी == * {{Cite journal |author = Michael Rowe |title = The Soviet pentecostal emigration movement |url = https://www.tandfonline.com/doi/epdf/10.1080/09637498308431098 |journal = Religion in Communist Lands |language = en |date = 2 January 2008 |volume = 11 |issue = 3 |pages = 337–339 |doi = 10.1080/09637498308431098 |ref = Rowe |url-access= subscription }} * {{Cite journal |author = Catherine Wanner |title = Religion and Refugee Resettlement: Evolving Connections to Ukraine since World War II |url = https://brill.com/view/journals/css/44/1-2/article-p44_4.xml |journal = Canadian-American Slavic Studies |language = en |publisher = Brill Schöningh |date = January 2010 |volume = 44 |issue = 1–2 |pages = 44–66 |issn = 2210-2396 |doi = 10.1163/221023910X512796 |ref = Wanner |url-access= subscription}} * {{Cite news|title=Soviet Pentecostals Meet Illegally and Form Council|last=Special To the New York Times|url=https://www.nytimes.com/1979/09/05/archives/soviet-pentecostals-meet-illegally-and-form-council-20-delegates.html|access-date=2025-12-18|lang=en|archive-url=https://web.archive.org/web/20180310060316/https://www.nytimes.com/1979/09/05/archives/soviet-pentecostals-meet-illegally-and-form-council-20-delegates.html|archive-date=2018-03-10}} {{Authority control}} [[श्रेणी:जीवित लोग]] dvmt2728n4ar3ru847kl70qpkvdkwxw