MediaWiki discusión:EtymologyFanInOut.js

Último comentario: hace 8 años por Peter Bowman en el tema Fan-out roto

Página de ayuda: Ayuda:Gadget-EtymologyFanInOut --95.22.18.73 16:13 15 ene 2012 (UTC)Responder

Fan-out roto editar

Hola, la recuperación de los aportes a otras lenguas actualmente no funciona correctamente. Sólo recupera la primera página por falta del parámetro rawcontinue. Por si es menester, adjunto versión revisada para controlar mejor las llamadas a la API.

var EtymologyFanInOut = {

//     API_URL: 'https://es.wiktionary.org/w/api.php?callback=?',
    API_URL: '//es.wiktionary.org/w/api.php?callback=?',

    NS14: 'Categoría:',

    /*
     * API fair use control
     */
    SYNC: true,
    CHUNK_SIZE: 4096,
    THROTTLE: 20,

    /*
     * legend
     */
    txtTitle: "Aportes desde y hacia otras lenguas",
    txtTitleFanIn: "Aportes desde otras lenguas y formación interna de palabras",
    txtTitleFanOut: "Aportes a otras lenguas",

    /**
     * @param langCode reference language code
     * @param dom_element DOM element to where the info will be attached
     */
    init: function (langCode, dom_element) {
        this.LANG_CODE = langCode || "es";
        this.addLayout(dom_element || document.body);
    },

    /**
     * @param element a DOM selector
     */
    addLayout: function (element) {
        var self = this;

        $('<table class="collapsible" style="border:dotted thin gray;width:100%;">' +
            '<caption style="cursor:pointer;font-weight:bold;">' + this.txtTitle + '</caption>' +
            '<tr><td><div id="etymfaninout" style="width:100%;"></div></td></tr></table>'
            ).appendTo(element);

        $('<span style="float:left;display:inline-block;">' +
            '<input type="submit" id="etymfanin-aceptar" value="' + this.txtTitleFanIn + '">' +
            '<span id="etymfanin-contenido" style="display:none;"></span>' +
            '<span id="etymfanin-status"></span></span>"'
            ).appendTo('#etymfaninout');

        $('<span style="float:right;display:inline-block;">' +
            '<input type="submit" id="etymfanout-aceptar" value="' + this.txtTitleFanOut + '">' +
            '<span id="etymfanout-contenido" style="display:none;"></span>' +
            '<span id="etymfanout-status"></span></span>"'
            ).appendTo('#etymfaninout');

        $('<span id="etymfanin-cabecera"></span>' +
            '<table class="sortable wikitable" style="text-align:right;">' +
            '<caption>' + this.txtTitleFanIn + '</caption><thead><tr style="text-align:center;">' +
            '<th>idioma</th><th>peso</th><th>cantidad</th></tr></thead>' +
            '<tbody id="etymfanin-lista"></tbody></table>'
            ).appendTo('#etymfanin-contenido');

        $('<span id="etymfanout-cabecera"></span>' +
            '<table class="sortable wikitable" style="text-align:right;">' +
            '<caption>' + this.txtTitleFanOut + '</caption><thead><tr style="text-align:center;">' +
            '<th>idioma</th><th>aporte<br/>interno</th><th>aporte<br/>externo</th>' +
            '<th>confianza</th><th>peso</th><th>étimos</th><th>lemas</th></tr></thead>' +
            '<tbody id="etymfanout-lista"></tbody></table>'
            ).appendTo('#etymfanout-contenido');

        $('#etymfanin-aceptar').click(function () {
            $(this).hide();
            self.fanIn(self.LANG_CODE);
        });

        $('#etymfanout-aceptar').click(function () {
            $(this).hide();
            self.fanOut(self.LANG_CODE);
        });
    },

    /**
     * API call with continue support
     *
     * @param f_filter function(data) -> filtered data
     * @param query api query object
     * @param previousItems list of items retrieved in previous calls
     * @param ncall progress indicator
     *
     * @return a promise(data items)
     */
    api: function (f_filter, query, previousItems, ncall) {
        var self = this,
            def = $.Deferred();
        ncall = ncall || 1;

        if (!previousItems || !previousItems.length) {
            previousItems = [];
        }

        $.getJSON(this.API_URL, query)
            .done(function (response) {
                def.notify(ncall);
                // add items
                var items = previousItems.concat(f_filter(response, query));

                if (response.continue) {
                    self.api(f_filter,
                             $.extend(query, response.continue),
                             items,
                             ncall + 1)
                        .progress(function (n) {
                            def.notify(n);
                        })
                        .done(def.resolve)
                        .fail(def.reject);
                } else {
                    def.resolve(items);
                }
            })
            .fail(def.reject);

        return def.promise();
    },

    /**
     * retrieve a list from API
     *
     * @param query API query
     *
     * @returns a promise(data items)
     */
    apiList: function (query) {
        query = $.extend({
            action: "query",
            format: "json",
            list: "categorymembers",
        }, query);

        var f_filter = function (raw, query) {
            return raw.query[query.list];
        };
        return this.api(f_filter, query);
    },

    /**
     * retrieve expanded wikitext from API
     *
     * @param text wikitext
     *
     * @return a promise(<expanded wikitext>)
     */
    apiExpand: function (text) {
        var query = {
            action: "expandtemplates",
            format: "json",
            text: text,
            prop: "wikitext",
        };
        return this.api(function (raw, query) {
            return raw.expandtemplates[query.prop];
        }, query);
    },

    /**
     * Get a list of subcategories
     *
     * @param catName parent category name
     *
     * @returns a promise([{"title"}])
     */
    apiSubcategories: function (catName) {
        return this.apiList({
            list: "categorymembers",
            cmnamespace: 14,
            cmprop: "title",
            cmtitle: catName,
        });
    },

    /**
     * Expand wikitext for each item.
     *
     * The wikitext for each item is group in text chunks in order to reduce
     * remote calls. The number of calls are dependent of a certain global
     * parameters:
     *
     * - CHUNK_SIZE: max size of the text chunk allowed in a single call
     * - THROTTLE: max number of expensive function executions allowed in a single call
     * - SYNC: if 'true', no asynchronous calls are allowed.
     *
     * @param items a list of items
     * @param f_pre(item) -> wikitext : preprocess callback
     * @param f_post(item, response) -> new item : postprocess callback
     * @param n_expensive number of expensive functions for each item
     */
    apiExpandItems: function (items, f_pre, f_post, n_expensive) {
        n_expensive = n_expensive || 1;

        var self = this,
            def = $.Deferred(),
            throttle = Math.round(this.THROTTLE / n_expensive),
            chunks = [""],
            idxChunk = 0,
            idx,
            item,
            requests = [],
            responses = [],
            chainedCall,
            strRequest,
            // functions
            expandChunk,
            generateCall,
            next,
            postProcess;

        for (idx = 0; idx < items.length; idx += 1) {
            item = items[idx];

            if ((chunks[idxChunk].length > this.CHUNK_SIZE) ||
                    ((idx + 1) % throttle === 0)) {
                chunks.push("");
                idxChunk += 1;
            }
            chunks[idxChunk] += f_pre(item) + ",";
        }

        // bind local data to an apiExpand call
        generateCall = function (request, chunkId, maxId) {
            return function () {
                return self.apiExpand(request)
                    .done(function () {
                        def.notify(chunkId + 1, maxId);
                    });
            };
        };

        // save results and expand the next chunk
        next = function (callback) {
            return function (response) {
                responses.push(response);
                return callback();
            };
        };

        // call API
        for (idx = 0; idx < chunks.length; idx += 1) {
            strRequest = chunks[idx].slice(0, -1);

            expandChunk = generateCall(strRequest, idx, chunks.length);

            if (strRequest.length > 0) {
                if (this.SYNC) {
                    // synchronous calls
                    if (!chainedCall) {
                        chainedCall = expandChunk();
                    } else {
                        chainedCall = chainedCall
                            .then(next(expandChunk));
                    }
                } else {
                    // asynchronous calls
                    requests.push(expandChunk());
                }
            }
        }

        // save last response
        if (chainedCall) {
            chainedCall = chainedCall
                .then(function (response) {
                    responses.push(response);
                });
        }

        // process response
        postProcess = function (data) {
            var idxItem = 0,
                idx2,
                response,
                fragment,
                idxFrag;

            for (idx2 = 0; idx2 < data.length; idx2 += 1) {
                response = "[" + data[idx2] + "]";

                try {
                    fragment = $.parseJSON(response);
                } catch (e) {
                    def.reject();
                    return;
                }

                for (idxFrag = 0; idxFrag < fragment.length; idxFrag += 1, idxItem += 1) {
                    items[idxItem] = f_post(items[idxItem], fragment[idxFrag]);
                }
            }

            def.resolve(items);
        };

        if (chainedCall) {
            // sync
            chainedCall
                .done(function () {
                    postProcess(responses);
                })
                .fail(def.reject);
        } else {
            // async
            $.when.apply($, requests)
                .done(function () {
                    postProcess(arguments);
                })
                .fail(def.reject);
        }

        return def.promise();
    },

    /**
     * Get a list of information tables for categories of languages with etyma
     * from a given language.
     *
     * @param items [{"title": <category name>}]
     *
     * @returns a promise({"title","code","c","t"}) where:
     *
     *  - code: language code extracted from the category title
     *  - c: number of etyma from the reference language in the category language
     *  - t: number of etyma in the category language
     */
    apiCategoriesTo: function (items) {
        var self = this,
            reLangCode = new RegExp("^" + this.NS14 + "([^:]+):Palabras de origen"),
            out = [],
            idx,
            item,
            match,
            langCode;

        for (idx = 0; idx < items.length; idx += 1) {
            item = items[idx];
            match = reLangCode.exec(item.title);
            if (match && match.length > 1) {
                langCode = match[1].toLowerCase();

                out.push({
                    code: langCode,
                    title: item.title,
                });
            }
        }

        return this.apiExpandItems(
            out,
            // f_pre
            function (item) {
                return '{"name":"{{' + item.code + '|texto=x}}",' +
                    '"c":{{PAGESINCAT:' + item.title.substring(self.NS14.length) + '|R}},' +
                    '"t":{{PAGESINCAT:{{ucfirst:{{' + item.code + '|texto=x}}}}' +
                    (item.code !== 'es' ? '-Español' : '') + '|R}} }';
            },
            // f_post
            function (item, response) {
                return $.extend(item, response);
            },
            // expensive functions
            2
        );
    },

    /**
     * Get a list of information tables for etymology categories for a given language
     *
     * @param langCode reference language code
     * @param items [{"title": <category name>}]
     *
     * @returns a promise({"title", "catname", "name", "size"}) where:
     *
     *  - title: the category full name
     *  - catname: the catgory name with not category prefix
     *  - name: language name extracted from 'title'
     *  - size: number of pages in the category
     */
    apiCategoriesFrom: function (langCode, items) {

        var prefix = langCode + ':Palabras ',
            out = [],
            idx,
            item,
            catName,
            name;

        for (idx = 0; idx < items.length; idx += 1) {
            item = items[idx];

            catName = item.title.substring(this.NS14.length);
            name = catName
                .substring(prefix.length)
                .replace('de origen ', '')
                .replace(/(incierto|epónimo|onomatopéyico|con etimología libre)/, '*$1')
                .replace('formadas por ', '*');

            out.push({
                title: item.title,
                catname: catName,
                name: name
            });
        }

        return this.apiExpandItems(out,
            // f_pre
            function (item) {return "{{PAGESINCAT:" + item.catname + "|R}}"; },
            // f_post
            function (item, response) { item.size = response; return item; }
            );
    },

    /**
     * get global information for a language
     *
     * @param langCode language code
     *
     * @returns a promise({"name": <language name>, "size": <number of language pages>})
     */
    apiLanguageInfo: function (langCode) {
        langCode = langCode.toLowerCase();

        var def = $.Deferred(),
            text = '{"name":"{{' + langCode + '|texto=x}}"' +
                ',"size":{{PAGESINCAT:{{ucfirst:{{' + langCode + '|texto=x}}}}' +
                (langCode !== 'es' ? '-Español' : '') + '|R}}}';

        this.apiExpand(text)
            .done(function (data) {
                if (data && data.length) {
                    try {
                        var response = $.parseJSON(data[0]);
                        def.resolve(response);
                    } catch (e) {
                        def.reject();
                    }
                } else {
                    def.reject();
                }
            })
            .fail(def.reject);

        return def.promise();
    },

    /**
     * @param langCode a language code
     *
     * @returns a promise(<normalized language name>)
     */
    apiNormalizedName: function (langCode) {
        langCode = langCode.toLowerCase();

        var def = $.Deferred(),
            text = "{{normalizar categoría|" + langCode + "}}";

        this.apiExpand(text)
            .done(function (data) {
                if (data && data.length) {
                    def.resolve(data[0]);
                } else {
                    def.reject();
                }
            })
            .fail(def.reject);

        return def.promise();
    },

    /**
     * apply a color gradient to '$el' based on 'ratio'
     */
    colour: function ($el, ratio) {
        ratio += 0.0;

        var red = 255.0 * (100.0 - ratio) / 100.0,
            green = 255.0 * ratio / 100.0;

        $el.css(
            'background-color',
            'rgb(' + red.toFixed(0) + ',' + green.toFixed(0) + ',0)'
        );
    },

    /**
     * show fan-out table
     *
     * @param langCode Wiktionary language code
     */
    fanOut: function (langCode) {
        var self = this,
            $status = $("#etymfanout-status");

        langCode = langCode.toUpperCase();

        $status.text("[ recuperando nombre normalizado ]");

        this.apiNormalizedName(langCode)
            .done(function (name) {
                var catPage = self.NS14 + "Palabras de origen " + name;

                $status.text("[ recuperando categorías ]");

                self.apiSubcategories(catPage)
                    .progress(function (n) {
                        $status.text("[ recuperando categorías, parte " + n + " ]");
                    })
                    .done(function (items) {
                        self.apiCategoriesTo(items)
                            .progress(function (id, maxId) {
                                $status.text("[ recuperando cantidades, " + id +
                                    "/" + maxId + "]");
                            })
                            .done($.proxy(self.fanOutRender, self))
                            .fail(function () {
                                $status.text("fallo en expansión de wikicódigo");
                            });
                    });
            })
            .fail(function () {
                $status.text("fallo al recuperar nombre normalizado");
            });
    },

    /**
     * @param categories [{"name": <language name>,
     *                     "t": <number of dest language etyma>
     *                     "c": <number of dest language etyma from ref language >,
     *                    }]
     */
    fanOutRender: function (categories) {
        var accumScore = 0.0,
            accumIncidence = 0.0,
            idx,
            item,
            inn,
            out,
            percentage,
            $el,
            docFragment;

        for (idx = 0; idx < categories.length; idx += 1) {
            item = categories[idx];

            if (item.c) {
                accumIncidence += item.c;
            }
        }

        for (idx = 0; idx < categories.length; idx += 1) {
            item = categories[idx];

            if (item.c) {
                item.c += 0.0;
                item.t += 0.0;

                item.trust = Math.log(item.t);
                item.inn = item.c / item.t;
                item.out = item.c / accumIncidence;
                item.weight = (item.inn + item.out) * item.trust;
                accumScore += item.weight;
            }
        }

        categories.sort(function (x, y) {
            return y.weight - x.weight;
        });

        $("#etymfanout-status").empty();

        docFragment = document.createDocumentFragment();

        for (idx = 0; idx < categories.length; idx += 1) {
            item = categories[idx];

            if (item.c) {
                percentage = 100.0 * item.weight / accumScore;
                inn = 100.0 * item.inn;
                out = 100.0 * item.out;

                $el = $(
                    '<tr><th>' + item.name + '</th><td class="interno">' +
                        inn.toFixed(2) + '%</td><td class="externo">' +
                        out.toFixed(2) + '%</td><td class="fiel">' +
                        item.trust.toFixed(2) + '</td><td class="pct">' +
                        percentage.toFixed(2) + '%</td><td class="num">' +
                        item.c + '</td><td>' +
                        item.t + '</td></tr>'
                );
                this.colour($el.find(".pct"), percentage);

                docFragment.appendChild($el[0]);
            }
        }

        $("#etymfanout-lista").append(docFragment);

        $('#etymfanout-contenido').show();

    },

    /**
     * show fan-in table
     *
     * @param langCode Wiktionary language code
     */
    fanIn: function (langCode) {
        langCode = langCode.toUpperCase();
        var self = this,
            catPage = this.NS14 + langCode + ":Palabras por origen",
            $status = $("#etymfanin-status");

        $status.text("[ recuperando categorías ]");

        this.apiSubcategories(catPage)
            .progress(function (n) {
                $status.text("[ recuperando categorías, parte " + n + " ]");
            })
            .done(function (items) {
                $status.text("[ recuperando cantidades ]");
                $.when(
                    self.apiLanguageInfo(langCode),
                    self.apiCategoriesFrom(langCode, items)
                        .progress(function (id, maxId) {
                            $status.text("[ recuperando cantidades, " + id + "/" + maxId + " ]");
                        })
                )
                    .done($.proxy(self.fanInRender, self))
                    .fail(function () {
                        $status.text("fallo en expansión de wikicódigo");
                    });
            })
            .fail(function () {
                $status.text("fallo al recuperar lista de categorías");
            });
    },

    /**
     * @param langInfo {"name": <language name>, "size": <total number of pages>}
     * @param categories [{"name": <language name>, "size": <number of pages>}]
     */
    fanInRender: function (langInfo, categories) {
        var totalEtim = 0,
            idx,
            item,
            percentage,
            $el,
            docFragment;

        for (idx = 0; idx < categories.length; idx += 1) {
            totalEtim += categories[idx].size;
        }

        $("#etymfanin-status").empty();

        categories.sort(function (x, y) {
            return y.size - x.size;
        });

        $("#etymfanin-cabecera")
            .html("<span id='idioma'>" + langInfo.name + ": " + totalEtim +
                    " etimologías de </span><span id='total'>~" +
                    langInfo.size + "</span>");

        docFragment = document.createDocumentFragment();

        totalEtim += 0.0;

        for (idx = 0; idx < categories.length; idx += 1) {
            item = categories[idx];

            if (item.size) {
                percentage = 100.0 * item.size / totalEtim;
                $el = $(
                    '<tr><th>' + item.name + '</th><td class="pct">' +
                        percentage.toFixed(2) + '%</td><td class="num">' +
                        item.size + '</td></tr>'
                );
                this.colour($el.find(".pct"), percentage);
                docFragment.appendChild($el[0]);
            }
        }

        $('#etymfanin-lista').append(docFragment);

        $('#etymfanin-contenido').show();
    },
};

$(function () {
    if (etymFanInOutCode && etymFanInOutLayoutParent) {
        EtymologyFanInOut.init(etymFanInOutCode, etymFanInOutLayoutParent);
    }
});

Saludos --81.37.208.48 16:07 13 dic 2015 (UTC)Responder

Me apunto aquí la última versión de Juan renombrado para contrastarla con la actual: Usuario:Juan renombrado/etim.js. Peter Bowman (discusión) 16:34 5 feb 2016 (UTC)Responder
Volver a la página «EtymologyFanInOut.js».